UNPKG

kotatsu

Version:

Straightforward command line tool to setup a development environment for modern JavaScript.

332 lines (265 loc) 7.63 kB
/** * Kotatsu Compiler Creator * ========================= * * Function in charge of creating the webpack compilers for the library. */ var webpack = require('webpack'), nodeExternals = require('webpack-node-externals'), NodePlugin = require('./NodePlugin.js'), progress = require('./progress.js'), resolve = require('./resolve.js'), path = require('path'), _ = require('lodash'); /** * Constants. */ var NODE_ENVIRONMENT = { global: false, __filename: false, __dirname: false }; var DEFAULT_EXTENSIONS = ['.wasm', '.mjs', '.js', '.json']; var BABEL_ENV = resolve('@babel/preset-env'), BABEL_REACT = resolve('@babel/preset-react'), BABEL_OBJECT_REST_SPREAD = resolve('@babel/plugin-proposal-object-rest-spread'), BABEL_CLASS_PROPERTIES = resolve('@babel/plugin-proposal-class-properties'), BABEL_LOADER = resolve('babel-loader'), STYLE_LOADER = resolve('style-loader'), CSS_LOADER = resolve('css-loader'), SASS_LOADER = resolve('sass-loader'), TYPESCRIPT_LOADER = resolve('ts-loader'), YAML_LOADER = resolve('yaml-loader'), SOURCE_MAP_SUPPORT = resolve('source-map-support'); var HMR_FRONTEND_CLIENT = 'webpack-hot-middleware/client'; var KOTATSU_PLUGINS = { hmr: webpack.HotModuleReplacementPlugin, noErrors: webpack.NoEmitOnErrorsPlugin }; /** * Helpers. */ function getHMRClientQuery(entryArray) { var relevantEntry = _.find(entryArray, function(entry) { return !!~entry.indexOf(HMR_FRONTEND_CLIENT); }); if (!relevantEntry) return false; var query = _.last(relevantEntry.split('?')); return query; } function handleEntry(entry, hotClient) { var entryConfig; if (typeof entry === 'string' || Array.isArray(entry)) { entryConfig = [].concat(entry); if (hotClient) { // First we need to check if some query was passed to the client var query = getHMRClientQuery(entryConfig); if (query) hotClient += '?' + query; // Adding the HMR client to the entry array entryConfig.unshift(hotClient); } } else if (typeof entry === 'object') { entryConfig = {}; for (var k in entry) { entryConfig[k] = handleEntry(entry[k], hotClient); } } return entryConfig; } function checkUsedPlugins(plugins) { var used = {}, k; plugins.forEach(function(plugin) { for (k in KOTATSU_PLUGINS) { if (plugin instanceof KOTATSU_PLUGINS[k]) used[k] = true; } }); return used; } /** * Main function. */ module.exports = function createCompiler(opts) { var frontEnd = opts.side === 'front', backEnd = !frontEnd, usedPlugins = checkUsedPlugins(opts.config.plugins || []); var hot = opts.hot !== false; // NOTE: options take precedence over the webpack config var entry = opts.entry || opts.config.entry; var entryIsTs = false; // TODO: entry could be an array of strings, or other things // TODO: if needed, do this in `handleEntry` if (typeof entry === 'string') { var entryExt = path.extname(entry); entryIsTs = entryExt === '.ts' || entryExt === '.tsx'; } // Building the entry var hotClient = null; if (hot) { if (backEnd) hotClient = path.join(__dirname, '..', 'hot', 'client.js'); else hotClient = resolve(HMR_FRONTEND_CLIENT); } var entryConfig = handleEntry(entry, hotClient); // Building the output var outputConfig = _.merge( { publicPath: '/build/' }, opts.solvedOutput, opts.config.output || {}, opts.output ? opts.solvedOutput : {} ); // Creating the webpack config var config = { mode: opts.production ? 'production' : 'development', entry: entryConfig, output: outputConfig, plugins: opts.config.plugins || [], module: {}, optimization: {} }; // HMR & No Errors if (hot) { if (!usedPlugins.hmr) config.plugins.push(new webpack.HotModuleReplacementPlugin()); if (!usedPlugins.noErrors) config.plugins.push(new webpack.NoEmitOnErrorsPlugin()); } // Merging the user's config var mergeTarget = _.omit(opts.config || {}, ['entry', 'output', 'plugins']); config = _.merge({}, mergeTarget, config); // Additional options & plugins for production if (opts.production) { config.optimization.minimize = true; config.plugins.push(new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } })); } // Are we creating a config for backend? if (backEnd) { // Env config.context = opts.cwd; config.target = 'node'; config.node = NODE_ENVIRONMENT; // Registering node_modules as externals config.externals = [nodeExternals()]; // Applying the node plugin config.plugins.unshift(new NodePlugin()); } // Should we display a progress bar? if (opts.progress) config.plugins.push(progress()); // Do we want sourcemaps if (opts.sourceMaps || opts.devtool) { if (backEnd) { var sourceMapModulePath = SOURCE_MAP_SUPPORT, injectString = 'require(\'' + sourceMapModulePath + '\').install();'; config.plugins.push(new webpack.BannerPlugin({ banner: injectString, raw: true, entryOnly: false })); } config.devtool = opts.devtool || 'source-map'; } // Additional loaders var rules = config.module.rules || []; // - Babel & ES2015 var presets = opts.presets; var babelLoaderAlready = presets.some(function(p) { var pname = Array.isArray(p) ? p[0] : p; return pname === '@babel/preset-env'; }); if (!babelLoaderAlready) presets.push(BABEL_ENV); if (opts.jsx) presets.push([ BABEL_REACT, { pragma: opts.pragma } ]); var babel = { test: /\.jsx?$/, exclude: /(node_modules|bower_components)/, use: { loader: BABEL_LOADER, options: { presets: presets, // TODO: search for plugins before adding them plugins: [BABEL_CLASS_PROPERTIES, BABEL_OBJECT_REST_SPREAD] } } }; // We don't add the babel rule if the user already gave one // TODO: create a function for this and test other loaders also if (!rules.some(function(rule) { // TODO: what if rule.use is an array... return ( typeof rule.use === 'string' ? rule.use : rule.use.loader ) === 'babel-loader'; })) { rules.push(babel); } // - YAML Support rules.push({ test: /\.ya?ml$/, use: { loader: YAML_LOADER, options: { asJSON: true } }, type: 'json' }); // - TS Support if (entryIsTs || opts.typescript) { rules.push({ test: /\.tsx?$/, use: TYPESCRIPT_LOADER, exclude: /(node_modules|bower_components)/ }); } // - SCSS Support if (opts.sass) rules.push({ test: /\.s[ac]ss$/, use: [STYLE_LOADER, CSS_LOADER, SASS_LOADER] }); // - CSS Support rules.push({ test: /\.css$/, use: [STYLE_LOADER, CSS_LOADER] }); config.module.rules = rules; // Extensions var extensions = new Set(DEFAULT_EXTENSIONS); config.resolve = {}; if (opts.config && opts.config.resolve) { config.resolve = opts.config.resolve; if (config.resolve.extensions) { config.resolve.extensions.forEach(function(ext) { extensions.add(ext); }); } } extensions.add('.jsx'); if (entryIsTs || opts.typescript) { extensions.add('.ts'); extensions.add('.tsx'); } config.resolve.extensions = Array.from(extensions); // Shunting logs config.infrastructureLogging = { level: 'error' }; return webpack(config); };