diff --git a/packages/xarc-webpack/README.md b/packages/xarc-webpack/README.md new file mode 100644 index 000000000..f164dec5f --- /dev/null +++ b/packages/xarc-webpack/README.md @@ -0,0 +1,3 @@ +# @xarc/webpack + +Contains Webpack config partials for a React app to include in its `webpack.config.ts` diff --git a/packages/xarc-webpack/package.json b/packages/xarc-webpack/package.json index 836106e8c..7a03df746 100644 --- a/packages/xarc-webpack/package.json +++ b/packages/xarc-webpack/package.json @@ -21,6 +21,7 @@ "docs": "xrun xarc/docs" }, "files": [ + "dist", "lib", "src" ], @@ -135,5 +136,16 @@ "typedoc", "typescript" ] + }, + "fyn": { + "dependencies": { + "electrode-cdn-file-loader": "../electrode-cdn-file-loader", + "electrode-node-resolver": "../electrode-node-resolver", + "webpack-config-composer": "../webpack-config-composer" + }, + "devDependencies": { + "@xarc/app": "../xarc-app", + "@xarc/app-dev": "../xarc-app-dev" + } } } diff --git a/packages/xarc-webpack/src/index.ts b/packages/xarc-webpack/src/index.ts index f1dc0cfc1..893e8d782 100644 --- a/packages/xarc-webpack/src/index.ts +++ b/packages/xarc-webpack/src/index.ts @@ -1,26 +1,316 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -const { initWebpackConfigComposer, generateConfig } = require("./util/generate-config"); - -// https://stackoverflow.com/questions/40900791/cannot-redeclare-block-scoped-variable-in-unrelated-files -export {}; +export { initWebpackConfigComposer, generateConfig as compose } from "./util/generate-config"; // -// When clap execute a build task that involves invoking webpack it +// When xrun execute a build task that involves invoking webpack it // will check if user wants webpack to start with their webpack.config.js // If yes, then the task will set env ELECTRODE_WEBPACK_PROFILE to // the desired profile, so when webpack runs, it's passed to the // archetype, where we can check and load the correct internal // webpack config accordingly. // -const profile = process.env.ELECTRODE_WEBPACK_PROFILE || "production"; -const options = require(`./options/${profile}`); -const partials = require("./partials"); - -module.exports = { - initWebpackConfigComposer, - compose: generateConfig, - env: profile, - options, - partials +export const env = process.env.ELECTRODE_WEBPACK_PROFILE || "production"; +export const options = require(`./options/${env}`); +const genPartials = require("./partials"); + +const ConfigPartial = require("webpack-config-composer/lib/partial"); + +/** + * The webpack config partials this module provides for building a webapp + */ +export const partials = { + /** + * Some base webpack configuration + */ + baseOptions: genPartials["_base-options"], + /** + * configuration to setup the app's entry code + */ + entry: genPartials._entry, + /** + * setup for processing subapp chunks + */ + subappChunks: genPartials["_subapp-chunks"], + /** + * setup whether to auto bundle source to simulate node.js APIs + */ + node: genPartials._node, + /** + * setup the bundle code output + */ + output: genPartials._output, + /** + * setup module resolution + */ + resolve: genPartials._resolve, + /** + * setup resolveLoader option + */ + resolveLoader: genPartials["_resolve-loader"], + /** + * base setup for running Karma tests + */ + karmaBase: genPartials["_test-base"], + /** + * setup for karma test entry + */ + karmaEntry: genPartials["_test-entry"], + /** + * setup for karma test output + */ + karmaOutput: genPartials["_test-output"], + /** + * setup for karma test module resolve + */ + karmaResolve: genPartials["_test-resolve"], + + /** + * setup to use babel and babel-loader to transpile code + */ + babel: genPartials._babel, + /** + * setup CSS/styling support + */ + extractStyle: genPartials["_extract-style"], + /** + * setup @loadable/webpack-plugin for dynamic import loadable components + */ + loadable: genPartials._loadable, + /** + * setup loaders for font files like woff/woff2/eot/ttf + */ + fonts: genPartials._fonts, + /** + * setup loaders for handling images like jpeg/png/gif/svg + */ + images: genPartials._images, + /** + * setup loading non-js assets when running in SSR mode + */ + isomorphic: genPartials._isomorphic, + /** + * setup for PWA functionalities + */ + pwa: genPartials._pwa, + /** + * setup a plugin to capture stats and save as stats.json + */ + statsWriter: genPartials._stats, + /** + * setup for optimizing code for production + * + * With webpack 4 this is not really needed given that webpack4 automatically + * handles minification with the mode option. + */ + minify: genPartials._uglify, + /** + * setup for locale support + */ + locales: genPartials._locales, + /** + * setup a plugin that properly terminates webpack on failures + */ + fail: genPartials._fail, + + /** + * setup development tools and server + */ + dev: genPartials._dev, + dllEntry: genPartials["_dll-entry"], + dllOutput: genPartials["_dll-output"], + dllReference: genPartials["_dll-reference"], + dllLoad: genPartials["_dll-load"], + dll: genPartials._dll, + /** + * setup a plugin to do simple text base compile progress reporting + */ + progressSimple: genPartials["_simple-progress"], + /** + * setup source maps to be inline + */ + sourceMapsInline: genPartials["_sourcemaps-inline"], + /** + * setup source maps to be remote + */ + sourceMapsRemote: genPartials["_sourcemaps-remote"], + /** + * set webpack to development mode + */ + devMode: genPartials["_dev-mode"], + /** + * set webpack to production mode + */ + prodMode: genPartials["_prod-mode"] +}; + +// support legacy custom webpack config from user +Object.assign(partials, genPartials); + +/** + * Some predefined profiles that specified a list of partials in arrays. + * + * These profiles are available: + * - base - the base for everything + * - production - partials that are for production build only + * - development - partials that are for development only + * - karma - partials that are for running karma tests only + */ +export const profiles = { + /** + * The base feature that include all the partials for a webapp. + * These partials are included: baseOptions, entry, subappChunks, + * output, resolve, resolveLoader, babel, extractStyle, fonts, + * images, statsWriter, isomorphic, node + */ + base: [ + partials.baseOptions, + partials.entry, + partials.subappChunks, + partials.output, + partials.resolve, + partials.resolveLoader, + partials.babel, + partials.extractStyle, + partials.fonts, + partials.images, + partials.statsWriter, + partials.isomorphic, + partials.node + ], + /** + * Additional partials that are used for a production build + */ + production: [ + partials.prodMode, + partials.dllReference, + partials.minify, + partials.locales, + partials.sourceMapsRemote, + partials.progressSimple + ], + /** + * Additional partials that are specific for a development build + */ + development: [partials.devMode, partials.dev], + /** + * Additional partials that are specific for a build to run Karma tests + */ + karma: [ + partials.devMode, + partials.sourceMapsInline, + partials.progressSimple, + partials.karmaBase, + partials.karmaEntry, + partials.karmaOutput, + partials.karmaResolve + ] +}; + +/** + * Ordinary plain object that holds a webpack config + */ +export type PlainConfig = Record; + +import * as WebpackComposer from "webpack-config-composer"; + +/** + * + * Apply an array of partial webpack configs into `config` + * + * The partials in the array is applied from left to right so the right ones override left ones. + * + * You can get predefined partials from this module. For example, to add your own webpack config: + * + * In your `webpack.config.ts`: + * + * ```js + * import { profiles, applyPartials } from "@xarc/webpack" + * + * const myConfig = applyPartials({ + * // your base webpack configs that are OK to get override + * }, + * [ + * ...profiles.base, + * ...profiles.development, + * { + * // your own webpack config that will override + * // everything else goes here + * } + * ] + * ); + * + * export default myConfig; + * ``` + * + * + * @param config - the base config (will not be mutated) + * @param parts - array of partials to apply + * + * @returns a new config with all partials merged into `config` + */ +export function applyPartials( + config: PlainConfig = {}, + parts: (PlainConfig | typeof ConfigPartial)[] +) { + const composer = new WebpackComposer({ partials: parts }); + composer.addProfile("apply", {}); + let id = 1; + parts.forEach(p => { + if (p instanceof ConfigPartial) { + composer.addPartialToProfile(p._name, "apply", p.config, p.options); + } else { + composer.addPartialToProfile(`object-${Date.now()}-${id++}`, "apply", p, {}); + } + }); + return composer.compose(config, "apply"); +} + +/** + * Provide out of the box default webpack configs for various modes: + * + * Available configs: + * - development() - for development + * - production() - for building production + * - karma() - for running karma tests + */ +export const defaultConfigs = { + /** + * generate webpack config for development + * + * Basically: `applyPartials({}, [...profiles.base, ...profiles.development])` + * @param baseConfig - base config to merge into (not mutated) + * @param moreParts - more partials to apply (will override) + * @returns a new webpack config ready for use + */ + development( + baseConfig: PlainConfig = {}, + moreParts: (PlainConfig | typeof ConfigPartial)[] = [] + ) { + return applyPartials(baseConfig, [...profiles.base, ...profiles.development, ...moreParts]); + }, + + /** + * generate webpack config for production + * + * Basically: `applyPartials({}, [...profiles.base, ...profiles.production])` + * @param baseConfig - base config to merge into (not mutated) + * @param moreParts - more partials to apply (will override) + * @returns a new webpack config ready for use + */ + production(baseConfig: PlainConfig = {}, moreParts: (PlainConfig | typeof ConfigPartial)[] = []) { + return applyPartials(baseConfig, [...profiles.base, ...profiles.production, ...moreParts]); + }, + + /** + * generate webpack config for running karma tests + * + * Basically: `applyPartials({}, [...profiles.base, ...profiles.karma])` + * @param baseConfig - base config to merge into (not mutated) + * @param moreParts - more partials to apply (will override) + * @returns a new webpack config ready for use + */ + karma(baseConfig: PlainConfig = {}, moreParts: (PlainConfig | typeof ConfigPartial)[] = []) { + return applyPartials(baseConfig, [...profiles.base, ...profiles.karma, moreParts]); + } }; diff --git a/packages/xarc-webpack/src/partials/dll-entry.ts b/packages/xarc-webpack/src/partials/dll-entry.ts index 473ce9952..3ae6a93de 100644 --- a/packages/xarc-webpack/src/partials/dll-entry.ts +++ b/packages/xarc-webpack/src/partials/dll-entry.ts @@ -1,10 +1,14 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import * as Path from "path"; -const archetype = require("@xarc/app-dev/config/archetype")(); -const AppMode = archetype.AppMode; -const clientDllConfig = require(Path.resolve(AppMode.src.client, "dll.config.js")); -module.exports = () => ({ - entry: clientDllConfig -}); +const archetypeConfig = require("@xarc/app-dev/config/archetype"); + +module.exports = () => { + const AppMode = archetypeConfig().AppMode; + const clientDllConfig = require(Path.resolve(AppMode.src.client, "dll.config.js")); + + return { + entry: clientDllConfig + }; +}; diff --git a/packages/xarc-webpack/src/partials/extract-style.ts b/packages/xarc-webpack/src/partials/extract-style.ts index 0b6f09dfa..b87cb296a 100644 --- a/packages/xarc-webpack/src/partials/extract-style.ts +++ b/packages/xarc-webpack/src/partials/extract-style.ts @@ -4,7 +4,7 @@ import * as Path from "path"; -const archetype = require("@xarc/app-dev/config/archetype")(); +const archetypeConfig = require("@xarc/app-dev/config/archetype"); const detectCssModule = require("../util/detect-css-module"); @@ -71,6 +71,7 @@ function loadPostCss() { module.exports = function() { const isProduction = process.env.NODE_ENV === "production"; const isDevelopment = !isProduction; + const archetype = archetypeConfig(); const { hasPostCss, atImport, postcssPresetEnv, postcssLoader } = loadPostCss(); diff --git a/packages/xarc-webpack/src/partials/index.ts b/packages/xarc-webpack/src/partials/index.ts index f1a64635b..72bc6a95c 100644 --- a/packages/xarc-webpack/src/partials/index.ts +++ b/packages/xarc-webpack/src/partials/index.ts @@ -50,15 +50,13 @@ const orders = [ ]; const files = Fs.readdirSync(__dirname) - .filter( - x => x !== "index.js" && !x.endsWith(".d.js") && !x.endsWith(".map") && !x.endsWith(".ts") - ) + .filter(x => x !== "index.js" && !x.endsWith(".d.ts") && !x.endsWith(".map") && x !== "index.ts") .map(x => x.substr(0, x.length - 3)); const partials = files.reduce((a, p) => { const k = `_${p}`; assert(orders.indexOf(k) >= 0, `No default order specified for partial ${p}`); - a[k] = new Partial(k, { config: () => require(`./${p}`) }); + a[k] = new Partial(k, { config: require(`./${p}`) }); return a; }, {}); diff --git a/packages/xarc-webpack/src/util/generate-config.ts b/packages/xarc-webpack/src/util/generate-config.ts index cc3b23662..525497baa 100644 --- a/packages/xarc-webpack/src/util/generate-config.ts +++ b/packages/xarc-webpack/src/util/generate-config.ts @@ -65,7 +65,7 @@ function searchUserCustomConfig(options) { // create a webpack config composer and add it to options as composer // returns a new options copy // -function initWebpackConfigComposer(options) { +export function initWebpackConfigComposer(options) { options = Object.assign({ profileNames: [] }, options); if (!options.composer) { @@ -80,7 +80,7 @@ function initWebpackConfigComposer(options) { return options; } -function generateConfig(opts, archetypeControl) { +export function generateConfig(opts, archetypeControl) { const options = initWebpackConfigComposer(opts); const { composer } = options; @@ -129,5 +129,3 @@ WARNING: `); return config; } - -module.exports = { initWebpackConfigComposer, generateConfig };