Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

const gulp = require('gulp');
const configs = require('./lib/gulp/configs.js');
const {generateBundle} = require('./lib/gulp/js.js');
const glob = require('util').promisify(require('glob'));

// Get all tasks
const clean = require('./lib/gulp/clean.js');
Expand All @@ -10,6 +12,20 @@ const {css, cssCompile} = require('./lib/gulp/css.js');
const {img, imgCompile, svgSprite} = require('./lib/gulp/image.js');
const {js, jsCompile, jsConcat} = require('./lib/gulp/js.js');

async function getDependencyTree(files, cwd) {
let dependencyTree = await Promise.all(files.map(async src => {
const bundle = await generateBundle({src, cwd});
return bundle.watchFiles;
}));

dependencyTree = dependencyTree
.flat()
// https://github.com/jlmakes/karma-rollup-preprocessor/issues/30
.filter(dependency => !dependency.includes('\u0000'));

return dependencyTree;
}

// Watch files
async function watchFiles() {
await configs.then(configurations => {
Expand All @@ -30,21 +46,34 @@ async function watchFiles() {
cssCompile({src: item.src, dest: item.dest});
});

config.js.forEach(j => {
config.js.forEach(async j => {
// Watch JS files, we name the function so that Gulp outputs the correct name
const files = await glob(j.src.join(), {absolute: true});
const dependencyTree = await getDependencyTree(files, j.cwd);

// eslint-disable-next-line func-names
gulp.watch(j.watch, function js() {
return jsCompile({src: j.src, dest: j.dest, cwd: config.cwd, browserSync});
gulp.watch(dependencyTree, function js() {
return Promise.all(files.map(async file => {
return jsCompile({
src: file,
dest: j.dest,
cwd: config.cwd,
browserSync
});
}));
});

// Compile JS once at watch startup
jsCompile({src: j.src, dest: j.dest});
await Promise.all(files.map(async file => jsCompile({src: file, dest: j.dest, cwd: config.cwd, browserSync})));
});

config['js-concat'].forEach(js => {
config['js-concat'].forEach(async js => {
// Watch JS files which need to be concatenated, we name the function so that Gulp outputs the correct name
const files = await glob(js.src.join(), {absolute: true});
const dependencyTree = await getDependencyTree(files, js.cwd);

// eslint-disable-next-line func-names
gulp.watch(js.watch, function concat() {
gulp.watch(dependencyTree, function concat() {
return jsConcat({src: js.src, dest: js.dest, js: js.name, browserSync});
});

Expand Down
19 changes: 17 additions & 2 deletions lib/gulp/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,24 @@ function processConfig(config, cwd) {
['css', 'js', 'img', 'js-concat', 'svg-sprite', 'copy'].forEach(key => {
if (config[key]) {
config[key] = config[key].map(value => {
const src = path.join(cwd, config.src_base_path, value.src);
let src = '';
if (Array.isArray(value.src)) {
src = value.src.map(src => path.join(cwd, config.src_base_path, src));
} else {
src = path.join(cwd, config.src_base_path, value.src);
}

const dest = path.join(cwd, config.dest_base_path, value.dest);
let watch = [src];

if (key === 'js' || key === 'js-concat') {
// Make sure JavaScript files are always arrays to prevent streamlined
src = Array.isArray(src) ? src : [src];
}

// Make sure watch entries are always arrays
// spreading of src array is to prevent copying with reference so
// src files do not get modified by watchConfigPaths
let watch = Array.isArray(src) ? [...src] : [src];

if (key === 'css') {
watch = watchConfigPaths(watch, 'stylelint', cwd);
Expand Down
151 changes: 81 additions & 70 deletions lib/gulp/js.js
Original file line number Diff line number Diff line change
@@ -1,107 +1,118 @@
const babel = require('gulp-babel');
const terser = require('gulp-terser');
const presetEnv = require('@babel/preset-env');
const concat = require('gulp-concat');
const gulp = require('gulp');
const gulpIf = require('gulp-if');
const {cosmiconfigSync} = require('cosmiconfig');
const eslint = require('gulp-eslint');
const plumber = require('gulp-plumber');
const {babel} = require('@rollup/plugin-babel');
const {eslint} = require('rollup-plugin-eslint');
const {rollup} = require('rollup');
const path = require('path');
const resolve = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const glob = require('util').promisify(require('glob'));
const {terser} = require('rollup-plugin-terser');
const configs = require('./configs.js');
const multi = require('@rollup/plugin-multi-entry');

async function js() {
let jsTasks = [];
let jsConcatTasks = [];

const configurations = await configs.then(configurations => {
configurations.forEach(config => {
jsTasks = jsTasks.concat(config.js.map(js => {
return jsCompile({src: js.src, dest: js.dest, cwd: config.cwd});
jsTasks = jsTasks.concat(config.js.map(async js => {
const src = await resolveAllAssetsFromArray(js.src);
return jsCompile({src, dest: js.dest, cwd: config.cwd});
}));
});
return configurations;
});

await Promise.all(jsTasks).then(() => {
configurations.forEach(config => {
jsConcatTasks = jsConcatTasks.concat(config['js-concat'].map(js => {
return jsConcat({src: js.src, dest: js.dest, name: js.name});
jsConcatTasks = jsConcatTasks.concat(config['js-concat'].map(async js => {
const src = await resolveAllAssetsFromArray(js.src);
return jsConcat({src, dest: js.dest, name: js.name, cwd: config.cwd});
}));
});
});

return Promise.all(jsConcatTasks);
}

function jsCompile({src, dest, cwd, browserSync = false}) {
return new Promise(
(async resolve => { // eslint-disable-line no-async-promise-executor
let stream = gulp
.src(src);

// Prevent errors from aborting task when files are being watched
if (process.argv.includes('watch')) {
stream = stream.pipe(plumber());
}

// Enable linting if configured
const eslintConfig = cosmiconfigSync('eslint').search(cwd);
if (eslintConfig) {
stream = stream.pipe(eslint({
fix: process.argv.includes('--fix')
}))
.pipe(gulpIf(file => file.eslint !== null && file.eslint.fixed, gulp.dest(file => file._base)))
.pipe(eslint.format());
}

stream = stream.pipe(babel({
presets: [presetEnv]
}));
async function resolveAllAssetsFromArray(assets) {
return (await Promise.all(assets.map(async asset => glob(asset)))).flat();
}

async function generateBundle({src, cwd, plugins = []}) {
const rollupPlugins = plugins;

// Bundle third party imported modules
rollupPlugins.push(resolve.default());

// Auto convert CommonJS modules to ES6, so they can be included in a Rollup bundle
rollupPlugins.push(commonjs());

// If user has an eslint configuration, load Eslint.
const hasEslintConfig = Boolean(cosmiconfigSync('eslint').search(cwd));
if (hasEslintConfig) {
rollupPlugins.push(eslint({fix: process.argv.includes('--fix')}));
}

if (process.env.NODE_ENV === 'production') {
stream = stream.pipe(terser());
}
// Decide whether to load a default babel preset if there is no babel configuration found
const hasBabelConfig = Boolean(cosmiconfigSync('babel').search(cwd));
const babelConfig = {babelHelpers: 'bundled'};
if (!hasBabelConfig) {
babelConfig.presets = ['@babel/preset-env'];
}

stream = stream.pipe(gulp.dest(dest));
rollupPlugins.push(babel(babelConfig));

if (browserSync !== false) {
stream.pipe(browserSync.stream({match: '**/*.js'}));
}
// Terser (minify)
rollupPlugins.push(terser());

stream.on('end', resolve);
})
);
return rollup({
input: src,
plugins: rollupPlugins
});
}

function jsConcat({src, dest, name, browserSync = false}) {
return new Promise(
(async resolve => { // eslint-disable-line no-async-promise-executor
let stream = gulp.src(src);
async function jsCompile({src, dest, cwd, browserSync = false}) {
const files = await glob(src.join(), {absolute: true});
if (files.length === 0) {
return;
}

const bundle = await generateBundle({src, cwd});

if (browserSync) {
browserSync.watch(bundle.watchFiles).on('change', browserSync.reload);
}

// Prevent errors from aborting task when files are being watched
if (process.argv.includes('watch')) {
stream = stream.pipe(plumber());
}
return bundle.write({
dir: dest,
format: 'amd',
sourcemap: process.env.NODE_ENV !== 'production'
});
}

stream = stream.pipe(plumber())
.pipe(babel({
presets: [presetEnv]
}))
.pipe(concat(name));
async function jsConcat({src, dest, name, browserSync = false, cwd}) {
// Abort if there are no files
const files = await glob(src.join(), {absolute: true});
if (files.length === 0) {
return;
}

if (process.env.NODE_ENV === 'production') {
stream = stream.pipe(terser());
}
// Allow multiple entry points to a single output
const plugins = [multi()];

stream = stream.pipe(gulp.dest(dest));
const bundle = await generateBundle({src, cwd, plugins});

if (browserSync !== false) {
stream.pipe(browserSync.stream({match: '**/*.js'}));
}
if (browserSync) {
browserSync.watch(bundle.watchFiles).on('change', browserSync.reload);
}

stream.on('end', resolve);
})
);
return bundle.write({
file: path.join(dest, name),
format: 'amd',
sourcemap: process.env.NODE_ENV !== 'production'
});
}

module.exports = {js, jsCompile, jsConcat};
module.exports = {js, jsCompile, jsConcat, generateBundle};
Loading