521 lines
17 KiB
JavaScript
521 lines
17 KiB
JavaScript
"use strict";
|
|
|
|
require(`v8-compile-cache`);
|
|
|
|
const fs = require(`fs-extra`);
|
|
|
|
const path = require(`path`);
|
|
|
|
const dotenv = require(`dotenv`);
|
|
|
|
const FriendlyErrorsWebpackPlugin = require(`@pieh/friendly-errors-webpack-plugin`);
|
|
|
|
const PnpWebpackPlugin = require(`pnp-webpack-plugin`);
|
|
|
|
const {
|
|
store
|
|
} = require(`../redux`);
|
|
|
|
const {
|
|
actions
|
|
} = require(`../redux/actions`);
|
|
|
|
const getPublicPath = require(`./get-public-path`);
|
|
|
|
const debug = require(`debug`)(`gatsby:webpack-config`);
|
|
|
|
const report = require(`gatsby-cli/lib/reporter`);
|
|
|
|
const {
|
|
withBasePath,
|
|
withTrailingSlash
|
|
} = require(`./path`);
|
|
|
|
const getGatsbyDependents = require(`./gatsby-dependents`);
|
|
|
|
const apiRunnerNode = require(`./api-runner-node`);
|
|
|
|
const createUtils = require(`./webpack-utils`);
|
|
|
|
const hasLocalEslint = require(`./local-eslint-config-finder`); // Four stages or modes:
|
|
// 1) develop: for `gatsby develop` command, hot reload and CSS injection into page
|
|
// 2) develop-html: same as develop without react-hmre in the babel config for html renderer
|
|
// 3) build-javascript: Build JS and CSS chunks for production
|
|
// 4) build-html: build all HTML files
|
|
|
|
|
|
module.exports = async (program, directory, suppliedStage) => {
|
|
const modulesThatUseGatsby = await getGatsbyDependents();
|
|
const directoryPath = withBasePath(directory);
|
|
process.env.GATSBY_BUILD_STAGE = suppliedStage; // We combine develop & develop-html stages for purposes of generating the
|
|
// webpack config.
|
|
|
|
const stage = suppliedStage;
|
|
const {
|
|
rules,
|
|
loaders,
|
|
plugins
|
|
} = await createUtils({
|
|
stage,
|
|
program
|
|
});
|
|
const {
|
|
assetPrefix,
|
|
pathPrefix
|
|
} = store.getState().config;
|
|
const publicPath = getPublicPath(Object.assign({
|
|
assetPrefix,
|
|
pathPrefix
|
|
}, program));
|
|
|
|
function processEnv(stage, defaultNodeEnv) {
|
|
debug(`Building env for "${stage}"`); // node env should be DEVELOPMENT | PRODUCTION as these are commonly used in node land
|
|
// this variable is used inside webpack
|
|
|
|
const nodeEnv = process.env.NODE_ENV || `${defaultNodeEnv}`; // config env is dependant on the env that it's run, this can be anything from staging-production
|
|
// this allows you to set use different .env environments or conditions in gatsby files
|
|
|
|
const configEnv = process.env.GATSBY_ACTIVE_ENV || nodeEnv;
|
|
const envFile = path.join(process.cwd(), `./.env.${configEnv}`);
|
|
let parsed = {};
|
|
|
|
try {
|
|
parsed = dotenv.parse(fs.readFileSync(envFile, {
|
|
encoding: `utf8`
|
|
}));
|
|
} catch (err) {
|
|
if (err.code !== `ENOENT`) {
|
|
report.error(`There was a problem processing the .env file (${envFile})`, err);
|
|
}
|
|
}
|
|
|
|
const envObject = Object.keys(parsed).reduce((acc, key) => {
|
|
acc[key] = JSON.stringify(parsed[key]);
|
|
return acc;
|
|
}, {});
|
|
const gatsbyVarObject = Object.keys(process.env).reduce((acc, key) => {
|
|
if (key.match(/^GATSBY_/)) {
|
|
acc[key] = JSON.stringify(process.env[key]);
|
|
}
|
|
|
|
return acc;
|
|
}, {}); // Don't allow overwriting of NODE_ENV, PUBLIC_DIR as to not break gatsby things
|
|
|
|
envObject.NODE_ENV = JSON.stringify(nodeEnv);
|
|
envObject.PUBLIC_DIR = JSON.stringify(`${process.cwd()}/public`);
|
|
envObject.BUILD_STAGE = JSON.stringify(stage);
|
|
envObject.CYPRESS_SUPPORT = JSON.stringify(process.env.CYPRESS_SUPPORT);
|
|
const mergedEnvVars = Object.assign(envObject, gatsbyVarObject);
|
|
return Object.keys(mergedEnvVars).reduce((acc, key) => {
|
|
acc[`process.env.${key}`] = mergedEnvVars[key];
|
|
return acc;
|
|
}, {
|
|
"process.env": JSON.stringify({})
|
|
});
|
|
}
|
|
|
|
function getHmrPath() {
|
|
// ref: https://github.com/gatsbyjs/gatsby/issues/8348
|
|
let hmrBasePath = `/`;
|
|
const hmrSuffix = `__webpack_hmr&reload=true&overlay=false`;
|
|
|
|
if (process.env.GATSBY_WEBPACK_PUBLICPATH) {
|
|
const pubPath = process.env.GATSBY_WEBPACK_PUBLICPATH;
|
|
|
|
if (pubPath.substr(-1) === `/`) {
|
|
hmrBasePath = pubPath;
|
|
} else {
|
|
hmrBasePath = withTrailingSlash(pubPath);
|
|
}
|
|
}
|
|
|
|
return hmrBasePath + hmrSuffix;
|
|
}
|
|
|
|
debug(`Loading webpack config for stage "${stage}"`);
|
|
|
|
function getOutput() {
|
|
switch (stage) {
|
|
case `develop`:
|
|
return {
|
|
path: directory,
|
|
filename: `[name].js`,
|
|
// Add /* filename */ comments to generated require()s in the output.
|
|
pathinfo: true,
|
|
// Point sourcemap entries to original disk location (format as URL on Windows)
|
|
publicPath: process.env.GATSBY_WEBPACK_PUBLICPATH || `/`,
|
|
devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, `/`),
|
|
// Avoid React cross-origin errors
|
|
// See https://reactjs.org/docs/cross-origin-errors.html
|
|
crossOriginLoading: `anonymous`
|
|
};
|
|
|
|
case `build-html`:
|
|
case `develop-html`:
|
|
// A temp file required by static-site-generator-plugin. See plugins() below.
|
|
// Deleted by build-html.js, since it's not needed for production.
|
|
return {
|
|
path: directoryPath(`public`),
|
|
filename: `render-page.js`,
|
|
libraryTarget: `umd`,
|
|
library: `lib`,
|
|
umdNamedDefine: true,
|
|
globalObject: `this`,
|
|
publicPath: withTrailingSlash(publicPath)
|
|
};
|
|
|
|
case `build-javascript`:
|
|
return {
|
|
filename: `[name]-[contenthash].js`,
|
|
chunkFilename: `[name]-[contenthash].js`,
|
|
path: directoryPath(`public`),
|
|
publicPath: withTrailingSlash(publicPath)
|
|
};
|
|
|
|
default:
|
|
throw new Error(`The state requested ${stage} doesn't exist.`);
|
|
}
|
|
}
|
|
|
|
function getEntry() {
|
|
switch (stage) {
|
|
case `develop`:
|
|
return {
|
|
commons: [require.resolve(`event-source-polyfill`), `${require.resolve(`webpack-hot-middleware/client`)}?path=${getHmrPath()}`, directoryPath(`.cache/app`)]
|
|
};
|
|
|
|
case `develop-html`:
|
|
return {
|
|
main: directoryPath(`.cache/develop-static-entry`)
|
|
};
|
|
|
|
case `build-html`:
|
|
return {
|
|
main: directoryPath(`.cache/static-entry`)
|
|
};
|
|
|
|
case `build-javascript`:
|
|
return {
|
|
app: directoryPath(`.cache/production-app`)
|
|
};
|
|
|
|
default:
|
|
throw new Error(`The state requested ${stage} doesn't exist.`);
|
|
}
|
|
}
|
|
|
|
function getPlugins() {
|
|
let configPlugins = [plugins.moment(), // Add a few global variables. Set NODE_ENV to production (enables
|
|
// optimizations for React) and what the link prefix is (__PATH_PREFIX__).
|
|
plugins.define(Object.assign({}, processEnv(stage, `development`), {
|
|
__BASE_PATH__: JSON.stringify(program.prefixPaths ? pathPrefix : ``),
|
|
__PATH_PREFIX__: JSON.stringify(program.prefixPaths ? publicPath : ``),
|
|
__ASSET_PREFIX__: JSON.stringify(program.prefixPaths ? assetPrefix : ``)
|
|
}))];
|
|
|
|
switch (stage) {
|
|
case `develop`:
|
|
configPlugins = configPlugins.concat([plugins.hotModuleReplacement(), plugins.noEmitOnErrors(), new FriendlyErrorsWebpackPlugin({
|
|
clearConsole: false
|
|
})]);
|
|
break;
|
|
|
|
case `build-javascript`:
|
|
{
|
|
configPlugins = configPlugins.concat([plugins.extractText(), // Write out stats object mapping named dynamic imports (aka page
|
|
// components) to all their async chunks.
|
|
plugins.extractStats()]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return configPlugins;
|
|
}
|
|
|
|
function getDevtool() {
|
|
switch (stage) {
|
|
case `develop`:
|
|
return `cheap-module-source-map`;
|
|
// use a normal `source-map` for the html phases since
|
|
// it gives better line and column numbers
|
|
|
|
case `develop-html`:
|
|
case `build-html`:
|
|
case `build-javascript`:
|
|
return `source-map`;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function getMode() {
|
|
switch (stage) {
|
|
case `build-javascript`:
|
|
return `production`;
|
|
|
|
case `develop`:
|
|
case `develop-html`:
|
|
case `build-html`:
|
|
return `development`;
|
|
// So we don't uglify the html bundle
|
|
|
|
default:
|
|
return `production`;
|
|
}
|
|
}
|
|
|
|
function getModule() {
|
|
// Common config for every env.
|
|
// prettier-ignore
|
|
let configRules = [rules.js({
|
|
modulesThatUseGatsby
|
|
}), rules.yaml(), rules.fonts(), rules.images(), rules.media(), rules.miscAssets(), // This is a hack that exports one of @reach/router internals (BaseContext)
|
|
// to export list. We need it to reset basepath and baseuri context after
|
|
// Gatsby main router changes it, to keep v2 behaviour.
|
|
// We will need to most likely remove this for v3.
|
|
{
|
|
test: require.resolve(`@reach/router/es/index`),
|
|
type: `javascript/auto`,
|
|
use: [{
|
|
loader: require.resolve(`./reach-router-add-basecontext-export-loader`)
|
|
}]
|
|
}]; // Speedup 🏎️💨 the build! We only include transpilation of node_modules on javascript production builds
|
|
// TODO create gatsby plugin to enable this behaviour on develop (only when people are requesting this feature)
|
|
|
|
if (stage === `build-javascript`) {
|
|
configRules.push(rules.dependencies({
|
|
modulesThatUseGatsby
|
|
}));
|
|
}
|
|
|
|
if (store.getState().themes.themes) {
|
|
configRules = configRules.concat(store.getState().themes.themes.map(theme => {
|
|
return {
|
|
test: /\.jsx?$/,
|
|
include: theme.themeDir,
|
|
use: [loaders.js()]
|
|
};
|
|
}));
|
|
}
|
|
|
|
switch (stage) {
|
|
case `develop`:
|
|
{
|
|
// get schema to pass to eslint config and program for directory
|
|
const {
|
|
schema,
|
|
program
|
|
} = store.getState(); // if no local eslint config, then add gatsby config
|
|
|
|
if (!hasLocalEslint(program.directory)) {
|
|
configRules = configRules.concat([rules.eslint(schema)]);
|
|
}
|
|
|
|
configRules = configRules.concat([{
|
|
oneOf: [rules.cssModules(), rules.css()]
|
|
}]); // RHL will patch React, replace React-DOM by React-🔥-DOM and work with fiber directly
|
|
// It's necessary to remove the warning in console (https://github.com/gatsbyjs/gatsby/issues/11934)
|
|
|
|
configRules.push({
|
|
include: /node_modules\/react-dom/,
|
|
test: /\.jsx?$/,
|
|
use: {
|
|
loader: require.resolve(`./webpack-hmr-hooks-patch`)
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
|
|
case `build-html`:
|
|
case `develop-html`:
|
|
// We don't deal with CSS at all when building the HTML.
|
|
// The 'null' loader is used to prevent 'module not found' errors.
|
|
// On the other hand CSS modules loaders are necessary.
|
|
// prettier-ignore
|
|
configRules = configRules.concat([{
|
|
oneOf: [rules.cssModules(), Object.assign({}, rules.css(), {
|
|
use: [loaders.null()]
|
|
})]
|
|
}]);
|
|
break;
|
|
|
|
case `build-javascript`:
|
|
// We don't deal with CSS at all when building JavaScript but we still
|
|
// need to process the CSS so offline-plugin knows about the various
|
|
// assets referenced in your CSS.
|
|
//
|
|
// It's also necessary to process CSS Modules so your JS knows the
|
|
// classNames to use.
|
|
configRules = configRules.concat([{
|
|
oneOf: [rules.cssModules(), rules.css()]
|
|
}]);
|
|
break;
|
|
}
|
|
|
|
return {
|
|
rules: configRules
|
|
};
|
|
}
|
|
|
|
function getResolve(stage) {
|
|
const {
|
|
program
|
|
} = store.getState();
|
|
const resolve = {
|
|
// Use the program's extension list (generated via the
|
|
// 'resolvableExtensions' API hook).
|
|
extensions: [...program.extensions],
|
|
alias: {
|
|
gatsby$: directoryPath(path.join(`.cache`, `gatsby-browser-entry.js`)),
|
|
// Using directories for module resolution is mandatory because
|
|
// relative path imports are used sometimes
|
|
// See https://stackoverflow.com/a/49455609/6420957 for more details
|
|
"@babel/runtime": path.dirname(require.resolve(`@babel/runtime/package.json`)),
|
|
"core-js": path.dirname(require.resolve(`core-js/package.json`)),
|
|
"react-hot-loader": path.dirname(require.resolve(`react-hot-loader/package.json`)),
|
|
"react-lifecycles-compat": directoryPath(`.cache/react-lifecycles-compat.js`),
|
|
"create-react-context": directoryPath(`.cache/create-react-context.js`)
|
|
},
|
|
plugins: [// Those two folders are special and contain gatsby-generated files
|
|
// whose dependencies should be resolved through the `gatsby` package
|
|
PnpWebpackPlugin.bind(directoryPath(`.cache`), module), PnpWebpackPlugin.bind(directoryPath(`public`), module), // Transparently resolve packages via PnP when needed; noop otherwise
|
|
PnpWebpackPlugin]
|
|
};
|
|
const target = stage === `build-html` || stage === `develop-html` ? `node` : `web`;
|
|
|
|
if (target === `web`) {
|
|
// force to use es modules when importing internals of @reach.router
|
|
// for browser bundles
|
|
resolve.alias[`@reach/router`] = path.join(path.dirname(require.resolve(`@reach/router/package.json`)), `es`);
|
|
}
|
|
|
|
return resolve;
|
|
}
|
|
|
|
function getResolveLoader() {
|
|
const root = [path.resolve(directory, `node_modules`)];
|
|
const userLoaderDirectoryPath = path.resolve(directory, `loaders`);
|
|
|
|
try {
|
|
if (fs.statSync(userLoaderDirectoryPath).isDirectory()) {
|
|
root.push(userLoaderDirectoryPath);
|
|
}
|
|
} catch (err) {
|
|
debug(`Error resolving user loaders directory`, err);
|
|
}
|
|
|
|
return {
|
|
modules: [...root, path.join(__dirname, `../loaders`), `node_modules`],
|
|
// Bare loaders should always be loaded via the user dependencies (loaders
|
|
// configured via third-party like gatsby use require.resolve)
|
|
plugins: [PnpWebpackPlugin.moduleLoader(`${directory}/`)]
|
|
};
|
|
}
|
|
|
|
const config = {
|
|
// Context is the base directory for resolving the entry option.
|
|
context: directory,
|
|
entry: getEntry(),
|
|
output: getOutput(),
|
|
module: getModule(),
|
|
plugins: getPlugins(),
|
|
// Certain "isomorphic" packages have different entry points for browser
|
|
// and server (see
|
|
// https://github.com/defunctzombie/package-browser-field-spec); setting
|
|
// the target tells webpack which file to include, ie. browser vs main.
|
|
target: stage === `build-html` || stage === `develop-html` ? `node` : `web`,
|
|
devtool: getDevtool(),
|
|
// Turn off performance hints as we (for now) don't want to show the normal
|
|
// webpack output anywhere.
|
|
performance: {
|
|
hints: false
|
|
},
|
|
mode: getMode(),
|
|
resolveLoader: getResolveLoader(),
|
|
resolve: getResolve(stage),
|
|
node: {
|
|
__filename: true
|
|
}
|
|
};
|
|
|
|
if (stage === `build-javascript`) {
|
|
config.optimization = {
|
|
runtimeChunk: {
|
|
name: `webpack-runtime`
|
|
},
|
|
splitChunks: {
|
|
name: false,
|
|
cacheGroups: {
|
|
// Only create one CSS file to avoid
|
|
// problems with code-split CSS loading in different orders
|
|
// causing inconsistent/non-determanistic styling
|
|
// See https://github.com/gatsbyjs/gatsby/issues/11072
|
|
styles: {
|
|
name: `styles`,
|
|
// This should cover all our types of CSS.
|
|
test: /\.(css|scss|sass|less|styl)$/,
|
|
chunks: `all`,
|
|
enforce: true
|
|
}
|
|
}
|
|
},
|
|
minimizer: [// TODO: maybe this option should be noMinimize?
|
|
!program.noUglify && plugins.minifyJs(), plugins.minifyCss()].filter(Boolean)
|
|
};
|
|
}
|
|
|
|
if (stage === `build-html` || stage === `develop-html`) {
|
|
// Packages we want to externalize to save some build time
|
|
// https://github.com/gatsbyjs/gatsby/pull/14208#pullrequestreview-240178728
|
|
const externalList = [`@reach/router/lib/history`, `@reach/router`, `common-tags`, /^core-js\//, `crypto`, `debug`, `fs`, `https`, `http`, `lodash`, `path`, `semver`, /^lodash\//, `zlib`]; // Packages we want to externalize because meant to be user-provided
|
|
|
|
const userExternalList = [`es6-promise`, `minimatch`, `pify`, `react-helmet`, `react`, /^react-dom\//];
|
|
|
|
const checkItem = (item, request) => {
|
|
if (typeof item === `string` && item === request) {
|
|
return true;
|
|
} else if (item instanceof RegExp && item.test(request)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
const isExternal = request => {
|
|
if (externalList.some(item => checkItem(item, request))) {
|
|
return `umd ${require.resolve(request)}`;
|
|
}
|
|
|
|
if (userExternalList.some(item => checkItem(item, request))) {
|
|
return `umd ${request}`;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
config.externals = [function (context, request, callback) {
|
|
const external = isExternal(request);
|
|
|
|
if (external !== null) {
|
|
callback(null, external);
|
|
} else {
|
|
callback();
|
|
}
|
|
}];
|
|
}
|
|
|
|
store.dispatch(actions.replaceWebpackConfig(config));
|
|
|
|
const getConfig = () => store.getState().webpack;
|
|
|
|
await apiRunnerNode(`onCreateWebpackConfig`, {
|
|
getConfig,
|
|
stage,
|
|
rules,
|
|
loaders,
|
|
plugins
|
|
});
|
|
return getConfig();
|
|
};
|
|
//# sourceMappingURL=webpack.config.js.map
|