How do I integrate PostCSS into Broccoli angular-cli Build? - broccolijs

It seems like this build script is running but the CSS that is output is not minified or auto prefixed. I am attempting to compile SASS, then run the output through Post CSS with Broccoli and the angular-cli. I figure maybe some ember-cli folks could help as well. What am I doing wrong?
The build outputs this in terminal:
Slowest Trees | Total
----------------------------------------------+---------------------
BroccoliTypeScriptCompiler | 1274ms
vendor | 502ms
PostcssFilter | 465ms
but the CSS is the same as if it were output from SASS, not Post CSS.
Here is my angular-cli-build.js:
'use strict';
/* global require, module */
const Angular2App = require('angular-cli/lib/broccoli/angular2-app');
const compileSass = require('broccoli-sass');
const compileCSS = require('broccoli-postcss');
const cssnext = require('postcss-cssnext');
const cssnano = require('cssnano');
const mergeTrees = require('broccoli-merge-trees');
const Funnel = require('broccoli-funnel');
const _ = require('lodash');
const glob = require('glob');
var options = {
plugins: [
{
module: cssnext,
options: {
browsers: ['> 1%'],
warnForDuplicates: false
}
},
{
module: cssnano,
options: {
safe: true,
sourcemap: true
}
}
]
};
module.exports = function(defaults) {
let sourceDir = 'src';
let appTree = new Angular2App(defaults, {
sourceDir: sourceDir,
sassCompiler: {
includePaths: [
'src/style'
]
},
vendorNpmFiles: [
'systemjs/dist/system-polyfills.js',
'systemjs/dist/system.src.js',
'zone.js/dist/*.js',
'es6-shim/es6-shim.js',
'reflect-metadata/*.js',
'reflect-metadata/*.js.map',
'rxjs/**/*.js',
'#angular/**/*.js',
'rxjs/**/*.js.map',
'#angular/**/*.js.map',
'd3/d3.js',
'three/build/three.js',
'three/examples/js/postprocessing/*.js',
'three/examples/js/shaders/*.js'
]
});
let sass = mergeTrees(_.map(glob.sync('src/**/*.scss'), function(sassFile) {
sassFile = sassFile.replace('src/', '');
return compileSass(['src'], sassFile, sassFile.replace(/.scss$/, '.css'));
}));
let css = compileCSS(sass, options);
return mergeTrees([sass, css, appTree], { overwrite: true });
};

It was the order of operations, my trees were not overwriting appTree!
This works!
return mergeTrees([appTree, sass, css], { overwrite: true });

Related

Webpack CopyPlugin : add file to plugin directory

I'd like to add the current module folder during my webpack compilation to my dist/ directory. For now, in /dist, I have something like that
const toCopy = [
'./../../../../node_modules/flatpickr/dist/flatpickr.min.js',
'./../../../../node_modules/flatpickr/dist/flatpickr.min.css',
]
this is the part of my webpack CopyPlugin
...
plugins: [
new CopyPlugin({
patterns: toCopy.map((entry) => {
console.log(entry);
return { from: entry }
})
})
]
And it gives me that
/dist
flatpickr.min.css
flatpickr.min.css
And I'd like that in order to avoid conflict if files from different modules have the same name .
for example, flatpickr as a ie.css, fr.js... but some other module could also have those files
/dist
/flatpickr
flatpickr.min.css
flatpickr.min.css
I've ended by created a loop over all my assets to copy to keep the CopyPlugin structure
const toCopy = new Map();
toCopy.set('popper', './../../contrib/bootstrap_sass/js/popper.min.js',);
toCopy.set('flatpickr', ['./../../../../node_modules/flatpickr/dist/flatpickr.min.js',
'./../../../../node_modules/flatpickr/dist/flatpickr.min.css']);
let toCopyFormatted = [];
for (let [dest, entries] of toCopy.entries()) {
if (typeof entries === 'string') {
let data = {to: dest}
data['from'] = entries;
toCopyFormatted.push(data);
} else {
entries.forEach(function (v, k) {
let data = {to: dest}
data['from'] = v;
toCopyFormatted.push(data);
})
}
}

Vue.js: Is it possible to do "Conditional compilation" with a Vue project?

In for example Swift/iOS development, it's possible to differentiate builds for different environments with "flags" such as:
#if STAGING
// one set of logic here
#endif
#if PRODUCTION
// another set of logic here
#endif
Is it possible to achieve the same with a Vue.js project, and how would we go about doing it? I am aware of makes different routes conditionally available for different roles (which is also quite neat), but I am optimally looking for the option to differentiate on a source code level.
Hope someone has some great insights! It could include:
How to exclude parts of a file (such as the #if STAGING above) from a build target
How to exclude entire files from a build target
etc.
you have the ability to use this syntax
if(process.env.NODE_ENV === 'production') {
console.log("this is the prod env!!!!!!!!!!");
config.output.path = path.resolve(__dirname, "dist");
}
make sure that when you run the script with the correct env's for each environment (local, dev, staging, prod etc ..) :D
just change the vue-loader output.
the source code
<template v-if="process.env.NODE_ENV === 'development'">
development only
</template>
default output
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
{ attrs: { id: "app" } },
[
_vm.process.env.NODE_ENV === "development"
? [_vm._v(" development only ")]
: _vm._e(),
_c("router-view")
],
2
)
}
just use regex to replace _vm.process.env. by process.env is ok.
// webpack.config.js
module: {
rules: [{
// must set post
enforce: 'post',
test: /\.vue$/,
use: [{
loader: './myLoader'
}]
}]
}
// myLoader.js
module.exports = function (source, map) {
if (source.indexOf('_vm.process.env') > -1) {
source = source.replace(/_vm.process.env/g, 'process.env')
}
this.callback(
null,
source,
map
)
}
Final the vue-loader result change
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
{ attrs: { id: "app" } },
[
// change to true
true
? [_vm._v(" development only ")]
: undefined,
_c("router-view")
],
2
)
}

Load CSS from a dynamically imported ES6 module

My project is being built with Webpack via Laravel Mix. I want to dynamically import an ES6 module that itself imports other modules and a stylesheet. Here is the dynamically imported module (loadJQueryTextillate.js):
import style from 'animate.css/animate.css';
import 'letteringjs';
import 'textillate';
style.use();
export default () => {
};
Here is the module that dynamically imports loadJQueryTextillate.js (animatedText.js):
import isInViewport from './isInViewport';
function maybeAnimateText( elem ) {
const $el = $( elem );
let bounding,
el_html,
el_lines,
in_viewport = $el.data( 'in-viewport' ) || false;
const viewport_height = window.innerHeight || document.documentElement.clientHeight;
if ( $el.hasClass( 'opaque' ) ) {
bounding = elem.getBoundingClientRect();
if ( in_viewport && !isInViewport( elem ) && ( bounding.top > viewport_height ) ) { // Element scrolled off screen
in_viewport = false;
$el.removeClass( 'opaque' ).find( 'ul.texts' ).remove().end().text( $.trim( $el.text() ) );
} else if ( isInViewport( elem ) ) {
in_viewport = true;
}
$el.data( 'in-viewport', in_viewport );
return;
} else if ( !isInViewport( elem ) ) {
return;
}
el_html = $el.html();
el_lines = el_html.split( /<br\s*\/?>/ );
$.each( el_lines, function( key, line ) {
el_lines[ key ] = $.trim( line );
} );
el_html = '<span class="line">' + el_lines.join( '</span><span class="line">' ) + '</span>';
import( /* webpackChunkName: "scripts/jQuery.textillate" */ './loadJQueryTextillate' ).then( () => {
$el.html( el_html ).addClass( 'opaque' ).children( '.line' ).textillate( {
in : {
effect : $el.data( 'in-effect' ) || 'fadeInLeft',
delay : $el.data( 'delay' ) || 12,
},
} );
} );
}
export default () => {
const $els = $( '.tlt' );
if ( 0 === $els.length ) {
return false;
}
$els.each( function( index, elem ) {
maybeAnimateText( elem );
} );
return true;
};
Here is the JS entry script (app.js):
window.$ = window.jQuery = require( 'jquery' );
import 'bootstrap';
import checkAnimatedText from './modules/animatedText';
$( window ).on( 'load', () => {
checkAnimatedText();
} );
Finally, here is the Laravel Mix config script (webpack.mix.js):
const mix = require( 'laravel-mix' );
require( 'laravel-mix-versionhash' );
// Public path helper
const publicPath = path => `${mix.config.publicPath}/${path}`;
// Source path helper
const src = path => `resources/assets/${path}`;
// Public Path
mix
.setPublicPath( './dist' )
.setResourceRoot( `/wp-content/themes/magnetar/${mix.config.publicPath}/` )
.webpackConfig( {
module : {
rules : [ {
test : /animate\.css$/,
use : [ {
loader : "style-loader/useable",
}, { loader : "css-loader" } ],
} ],
},
output : { publicPath : mix.config.resourceRoot },
} );
// Browsersync
mix.browserSync( 'magnetar.localhost' );
// Styles
mix.sass( src`styles/app.scss`, 'styles' );
// Assets
mix.copyDirectory( src`images`, publicPath`images` )
.copyDirectory( src`fonts`, publicPath`fonts` );
// JavaScript
mix.js( src`scripts/app.js`, 'scripts' );
//.extract();
// Autoload
/*mix.autoload( {
jquery : [ '$', 'window.jQuery' ],
} );*/
// Source maps when not in production.
mix.sourceMaps( false, 'source-map' );
// Hash and version files in production.
mix.versionHash( { length : 16 } );
Compiler output:
ERROR in ./node_modules/animate.css/animate.css (./node_modules/css-loader??ref--6-1!./node_modules/postcss-loader/src??ref--6-2!./node_modules/style-loader/useable.js!./node_modules/css-loader!./node_modules/animate.css/animate.css)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
SyntaxError
(1:1) Unknown word
> 1 | var refs = 0;
| ^
2 | var dispose;
3 | var content = require("!!../css-loader/index.js!./animate.css");
EDIT: Updated contents of loadJQueryTextillate.js, webpack.mix.js and compiler output.
You can try style-loader/useable to dynamically load css file. In your script code, you should use style.use() to make style useable or use style.unuse() to make style disable.
The following code shows how you should do to use style-loader/useable.
webpack.config.js
{
module: {
rules: [
{
test: /\.css$/,
exclude: /\.useable\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
],
},
{
test: /\.useable\.css$/,
use: [
{
loader: "style-loader/useable"
},
{ loader: "css-loader" },
],
},
],
},
}
file you want to dynamically load animate.css
import style form './animate.css';
// make aniamte.css useable
style.use();
// make animate.css disable
style.unuse();

Trying to get gulp notify to work and tell me things are running and updating

I am currently struggling to get gulp notify to display a success notification when I run my gulp watch command from terminal so that I know the command is running and the files have been compiled to the dist directory I have created.
I am very new to gulp and reading the docs on notify isn't 100% easy for me to understand or clear to me what I am doing wrong.
currently the code looks a bit like this...
var gulp = require('gulp'),
gutil = require('gulp-util'),
jshint = require('gulp-jshint'),
uglify = require('gulp-uglify'),
sass = require('gulp-sass'),
concat = require('gulp-concat'),
sourcemaps = require('gulp-sourcemaps'),
pump = require('pump'),
notify = require('gulp-notify'),
plumber = require('gulp-plumber'),
growl = require('growl'),
input = {
'sass': 'scss/**/*.scss',
'javascript': 'js/**/*.js'
},
output = {
'stylesheets': 'dist/styles',
'javascript': 'dist/javascript'
};
gulp.task('build-js', function() {
return gulp.src(input.javascript)
.pipe(sourcemaps.init())
.pipe(concat('test.js'))
//only uglify if gulp is ran with '--type production'
.pipe(gutil.env.type === 'production' ? uglify() : gutil.noop())
.pipe(sourcemaps.write())
.pipe(gulp.dest(output.javascript));
});
gulp.task('build-css', function() {
return gulp.src('scss/**/*.scss')
.pipe(sourcemaps.init())
.pipe(sass())
.pipe(sourcemaps.write())
.pipe(gulp.dest(output.stylesheets));
});
gulp.task('watch', function() {
notify("Watching for changes...").write('');
gulp.watch(input.javascript, ['jshint', 'build-js']);
gulp.watch(input.sass, ['build-css']);
.pipe(notify({
message: "Generated file: <%= file.relative %> # <%= options.date %>",
templateOptions: {
date: new Date()
}
}))
});
I've managed to get it working by changing the watch task to the following
gulp.task('watch', function() {
notify("Watching for changes...").write('');
gulp.watch(input.javascript, ['jshint', 'build-js']);
gulp.watch(input.sass, ['build-css']);
gulp.watch([
'dist/**/*'
]).on('change', function () {
notify("Files updated").write('');
});
});

How to test AngularJS Directive with scrolling

I have an infinite scroll directive that I am trying to unit test. Currently I have this:
describe('Infinite Scroll', function(){
var $compile, $scope;
beforeEach(module('nag.infiniteScroll'));
beforeEach(inject(function($injector) {
$scope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
$scope.scrolled = false;
$scope.test = function() {
$scope.scrolled = true;
};
}));
var setupElement = function(scope) {
var element = $compile('<div><div id="test" style="height:50px; width: 50px;overflow: auto" nag-infinite-scroll="test()">a<br><br><br>c<br><br><br><br>e<br><br>v<br><br>f<br><br>g<br><br>m<br>f<br><br>y<br></div></div>')(scope);
scope.$digest();
return element;
}
it('should have proper initial structure', function() {
var element = setupElement($scope);
element.find('#test').scrollTop(10000);
expect($scope.scrolled).toBe(true);
});
});
However the .scrollTop(10000); does not seem to trigger anything.
Is there anyway to unit test this type of functionality (I am able to unit test other directives with similar interactions like clicking on elements)?
In case it is relative, this is the infinite scroll code:
angular.module('nag.infiniteScroll', [])
.directive('nagInfiniteScroll', [
function() {
return function(scope, elm, attr) {
var raw = elm[0];
elm.bind('scroll', function() {
if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
scope.$apply(attr.nagInfiniteScroll);
}
});
};
}
]);
You have to trigger the scroll event on your element manually in your test:
element.find('#test').scrollTop(10000);
element.find('#test').triggerHandler('scroll');
Had the same issue recently. For the scrolling to work, you will need to set some dimensions on the body tag, so the window can be scrolled.
var scrollEvent = document.createEvent( 'CustomEvent' ); // MUST be 'CustomEvent'
scrollEvent.initCustomEvent( 'scroll', false, false, null );
var expectedLeft = 123;
var expectedTop = 456;
mockWindow.document.body.style.minHeight = '9000px';
mockWindow.document.body.style.minWidth = '9000px';
mockWindow.scrollTo( expectedLeft, expectedTop );
mockWindow.dispatchEvent( scrollEvent );
Unfortunately this does not work in PhantomJS.
If you are running your tests on Travis CI, you can also use Chrome by adding the following to your .travis.yml
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
And a custom Chrome launcher in your karma config:
module.exports = function(config) {
var configuration = {
// ... your default content
// This is the new content for your travis-ci configuration test
// Custom launcher for Travis-CI
customLaunchers: {
Chrome_travis_ci: {
base: 'Chrome',
flags: ['--no-sandbox']
}
},
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: true
};
if(process.env.TRAVIS){
configuration.browsers = ['Chrome_travis_ci'];
}
config.set( configuration );
};