UNPKG

18.1 kBJavaScriptView Raw
1"use strict";
2
3require(`v8-compile-cache`);
4
5const fs = require(`fs-extra`);
6
7const path = require(`path`);
8
9const dotenv = require(`dotenv`);
10
11const PnpWebpackPlugin = require(`pnp-webpack-plugin`);
12
13const {
14 store
15} = require(`../redux`);
16
17const {
18 actions
19} = require(`../redux/actions`);
20
21const getPublicPath = require(`./get-public-path`);
22
23const debug = require(`debug`)(`gatsby:webpack-config`);
24
25const report = require(`gatsby-cli/lib/reporter`);
26
27const {
28 withBasePath,
29 withTrailingSlash
30} = require(`./path`);
31
32const getGatsbyDependents = require(`./gatsby-dependents`);
33
34const apiRunnerNode = require(`./api-runner-node`);
35
36const createUtils = require(`./webpack-utils`);
37
38const hasLocalEslint = require(`./local-eslint-config-finder`); // Four stages or modes:
39// 1) develop: for `gatsby develop` command, hot reload and CSS injection into page
40// 2) develop-html: same as develop without react-hmre in the babel config for html renderer
41// 3) build-javascript: Build JS and CSS chunks for production
42// 4) build-html: build all HTML files
43
44
45module.exports = async (program, directory, suppliedStage, port, {
46 parentSpan
47} = {}) => {
48 const modulesThatUseGatsby = await getGatsbyDependents();
49 const directoryPath = withBasePath(directory);
50 process.env.GATSBY_BUILD_STAGE = suppliedStage; // We combine develop & develop-html stages for purposes of generating the
51 // webpack config.
52
53 const stage = suppliedStage;
54 const {
55 rules,
56 loaders,
57 plugins
58 } = await createUtils({
59 stage,
60 program
61 });
62 const {
63 assetPrefix,
64 pathPrefix
65 } = store.getState().config;
66 const publicPath = getPublicPath(Object.assign({
67 assetPrefix,
68 pathPrefix
69 }, program));
70
71 function processEnv(stage, defaultNodeEnv) {
72 debug(`Building env for "${stage}"`); // node env should be DEVELOPMENT | PRODUCTION as these are commonly used in node land
73 // this variable is used inside webpack
74
75 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
76 // this allows you to set use different .env environments or conditions in gatsby files
77
78 const configEnv = process.env.GATSBY_ACTIVE_ENV || nodeEnv;
79 const envFile = path.join(process.cwd(), `./.env.${configEnv}`);
80 let parsed = {};
81
82 try {
83 parsed = dotenv.parse(fs.readFileSync(envFile, {
84 encoding: `utf8`
85 }));
86 } catch (err) {
87 if (err.code !== `ENOENT`) {
88 report.error(`There was a problem processing the .env file (${envFile})`, err);
89 }
90 }
91
92 const envObject = Object.keys(parsed).reduce((acc, key) => {
93 acc[key] = JSON.stringify(parsed[key]);
94 return acc;
95 }, {});
96 const gatsbyVarObject = Object.keys(process.env).reduce((acc, key) => {
97 if (key.match(/^GATSBY_/)) {
98 acc[key] = JSON.stringify(process.env[key]);
99 }
100
101 return acc;
102 }, {}); // Don't allow overwriting of NODE_ENV, PUBLIC_DIR as to not break gatsby things
103
104 envObject.NODE_ENV = JSON.stringify(nodeEnv);
105 envObject.PUBLIC_DIR = JSON.stringify(`${process.cwd()}/public`);
106 envObject.BUILD_STAGE = JSON.stringify(stage);
107 envObject.CYPRESS_SUPPORT = JSON.stringify(process.env.CYPRESS_SUPPORT);
108 const mergedEnvVars = Object.assign(envObject, gatsbyVarObject);
109 return Object.keys(mergedEnvVars).reduce((acc, key) => {
110 acc[`process.env.${key}`] = mergedEnvVars[key];
111 return acc;
112 }, {
113 "process.env": JSON.stringify({})
114 });
115 }
116
117 function getHmrPath() {
118 // ref: https://github.com/gatsbyjs/gatsby/issues/8348
119 let hmrBasePath = `/`;
120 const hmrSuffix = `__webpack_hmr&reload=true&overlay=false`;
121
122 if (process.env.GATSBY_WEBPACK_PUBLICPATH) {
123 const pubPath = process.env.GATSBY_WEBPACK_PUBLICPATH;
124
125 if (pubPath.substr(-1) === `/`) {
126 hmrBasePath = pubPath;
127 } else {
128 hmrBasePath = withTrailingSlash(pubPath);
129 }
130 }
131
132 return hmrBasePath + hmrSuffix;
133 }
134
135 debug(`Loading webpack config for stage "${stage}"`);
136
137 function getOutput() {
138 switch (stage) {
139 case `develop`:
140 return {
141 path: directory,
142 filename: `[name].js`,
143 // Add /* filename */ comments to generated require()s in the output.
144 pathinfo: true,
145 // Point sourcemap entries to original disk location (format as URL on Windows)
146 publicPath: process.env.GATSBY_WEBPACK_PUBLICPATH || `/`,
147 devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, `/`),
148 // Avoid React cross-origin errors
149 // See https://reactjs.org/docs/cross-origin-errors.html
150 crossOriginLoading: `anonymous`
151 };
152
153 case `build-html`:
154 case `develop-html`:
155 // A temp file required by static-site-generator-plugin. See plugins() below.
156 // Deleted by build-html.js, since it's not needed for production.
157 return {
158 path: directoryPath(`public`),
159 filename: `render-page.js`,
160 libraryTarget: `umd`,
161 library: `lib`,
162 umdNamedDefine: true,
163 globalObject: `this`,
164 publicPath: withTrailingSlash(publicPath)
165 };
166
167 case `build-javascript`:
168 return {
169 filename: `[name]-[contenthash].js`,
170 chunkFilename: `[name]-[contenthash].js`,
171 path: directoryPath(`public`),
172 publicPath: withTrailingSlash(publicPath)
173 };
174
175 default:
176 throw new Error(`The state requested ${stage} doesn't exist.`);
177 }
178 }
179
180 function getEntry() {
181 switch (stage) {
182 case `develop`:
183 return {
184 commons: [require.resolve(`event-source-polyfill`), `${require.resolve(`webpack-hot-middleware/client`)}?path=${getHmrPath()}`, directoryPath(`.cache/app`)]
185 };
186
187 case `develop-html`:
188 return {
189 main: directoryPath(`.cache/develop-static-entry`)
190 };
191
192 case `build-html`:
193 return {
194 main: directoryPath(`.cache/static-entry`)
195 };
196
197 case `build-javascript`:
198 return {
199 app: directoryPath(`.cache/production-app`)
200 };
201
202 default:
203 throw new Error(`The state requested ${stage} doesn't exist.`);
204 }
205 }
206
207 function getPlugins() {
208 let configPlugins = [plugins.moment(), // Add a few global variables. Set NODE_ENV to production (enables
209 // optimizations for React) and what the link prefix is (__PATH_PREFIX__).
210 plugins.define(Object.assign({}, processEnv(stage, `development`), {
211 __BASE_PATH__: JSON.stringify(program.prefixPaths ? pathPrefix : ``),
212 __PATH_PREFIX__: JSON.stringify(program.prefixPaths ? publicPath : ``),
213 __ASSET_PREFIX__: JSON.stringify(program.prefixPaths ? assetPrefix : ``)
214 }))];
215
216 switch (stage) {
217 case `develop`:
218 configPlugins = configPlugins.concat([plugins.hotModuleReplacement(), plugins.noEmitOnErrors(), plugins.eslintGraphqlSchemaReload()]);
219 break;
220
221 case `build-javascript`:
222 {
223 configPlugins = configPlugins.concat([plugins.extractText(), // Write out stats object mapping named dynamic imports (aka page
224 // components) to all their async chunks.
225 plugins.extractStats()]);
226 break;
227 }
228 }
229
230 return configPlugins;
231 }
232
233 function getDevtool() {
234 switch (stage) {
235 case `develop`:
236 return `cheap-module-source-map`;
237 // use a normal `source-map` for the html phases since
238 // it gives better line and column numbers
239
240 case `develop-html`:
241 case `build-html`:
242 case `build-javascript`:
243 return `source-map`;
244
245 default:
246 return false;
247 }
248 }
249
250 function getMode() {
251 switch (stage) {
252 case `build-javascript`:
253 return `production`;
254
255 case `develop`:
256 case `develop-html`:
257 case `build-html`:
258 return `development`;
259 // So we don't uglify the html bundle
260
261 default:
262 return `production`;
263 }
264 }
265
266 function getModule() {
267 // Common config for every env.
268 // prettier-ignore
269 let configRules = [rules.js({
270 modulesThatUseGatsby
271 }), rules.yaml(), rules.fonts(), rules.images(), rules.media(), rules.miscAssets(), // This is a hack that exports one of @reach/router internals (BaseContext)
272 // to export list. We need it to reset basepath and baseuri context after
273 // Gatsby main router changes it, to keep v2 behaviour.
274 // We will need to most likely remove this for v3.
275 {
276 test: require.resolve(`@reach/router/es/index`),
277 type: `javascript/auto`,
278 use: [{
279 loader: require.resolve(`./reach-router-add-basecontext-export-loader`)
280 }]
281 }]; // Speedup 🏎️💨 the build! We only include transpilation of node_modules on javascript production builds
282 // TODO create gatsby plugin to enable this behaviour on develop (only when people are requesting this feature)
283
284 if (stage === `build-javascript`) {
285 configRules.push(rules.dependencies({
286 modulesThatUseGatsby
287 }));
288 }
289
290 if (store.getState().themes.themes) {
291 configRules = configRules.concat(store.getState().themes.themes.map(theme => {
292 return {
293 test: /\.jsx?$/,
294 include: theme.themeDir,
295 use: [loaders.js()]
296 };
297 }));
298 }
299
300 switch (stage) {
301 case `develop`:
302 {
303 // get schema to pass to eslint config and program for directory
304 const {
305 schema,
306 program
307 } = store.getState(); // if no local eslint config, then add gatsby config
308
309 if (!hasLocalEslint(program.directory)) {
310 configRules = configRules.concat([rules.eslint(schema)]);
311 }
312
313 configRules = configRules.concat([{
314 oneOf: [rules.cssModules(), rules.css()]
315 }]); // RHL will patch React, replace React-DOM by React-🔥-DOM and work with fiber directly
316 // It's necessary to remove the warning in console (https://github.com/gatsbyjs/gatsby/issues/11934)
317
318 configRules.push({
319 include: /node_modules\/react-dom/,
320 test: /\.jsx?$/,
321 use: {
322 loader: require.resolve(`./webpack-hmr-hooks-patch`)
323 }
324 });
325 break;
326 }
327
328 case `build-html`:
329 case `develop-html`:
330 // We don't deal with CSS at all when building the HTML.
331 // The 'null' loader is used to prevent 'module not found' errors.
332 // On the other hand CSS modules loaders are necessary.
333 // prettier-ignore
334 configRules = configRules.concat([{
335 oneOf: [rules.cssModules(), Object.assign({}, rules.css(), {
336 use: [loaders.null()]
337 })]
338 }]);
339 break;
340
341 case `build-javascript`:
342 // We don't deal with CSS at all when building JavaScript but we still
343 // need to process the CSS so offline-plugin knows about the various
344 // assets referenced in your CSS.
345 //
346 // It's also necessary to process CSS Modules so your JS knows the
347 // classNames to use.
348 configRules = configRules.concat([{
349 oneOf: [rules.cssModules(), rules.css()]
350 }]);
351 break;
352 }
353
354 return {
355 rules: configRules
356 };
357 }
358
359 function getResolve(stage) {
360 const {
361 program
362 } = store.getState();
363 const resolve = {
364 // Use the program's extension list (generated via the
365 // 'resolvableExtensions' API hook).
366 extensions: [...program.extensions],
367 alias: {
368 gatsby$: directoryPath(path.join(`.cache`, `gatsby-browser-entry.js`)),
369 // Using directories for module resolution is mandatory because
370 // relative path imports are used sometimes
371 // See https://stackoverflow.com/a/49455609/6420957 for more details
372 "@babel/runtime": path.dirname(require.resolve(`@babel/runtime/package.json`)),
373 "core-js": path.dirname(require.resolve(`core-js/package.json`)),
374 "react-hot-loader": path.dirname(require.resolve(`react-hot-loader/package.json`)),
375 "react-lifecycles-compat": directoryPath(`.cache/react-lifecycles-compat.js`),
376 "create-react-context": directoryPath(`.cache/create-react-context.js`)
377 },
378 plugins: [// Those two folders are special and contain gatsby-generated files
379 // whose dependencies should be resolved through the `gatsby` package
380 PnpWebpackPlugin.bind(directoryPath(`.cache`), module), PnpWebpackPlugin.bind(directoryPath(`public`), module), // Transparently resolve packages via PnP when needed; noop otherwise
381 PnpWebpackPlugin]
382 };
383 const target = stage === `build-html` || stage === `develop-html` ? `node` : `web`;
384
385 if (target === `web`) {
386 // force to use es modules when importing internals of @reach.router
387 // for browser bundles
388 resolve.alias[`@reach/router`] = path.join(path.dirname(require.resolve(`@reach/router/package.json`)), `es`);
389 }
390
391 return resolve;
392 }
393
394 function getResolveLoader() {
395 const root = [path.resolve(directory, `node_modules`)];
396 const userLoaderDirectoryPath = path.resolve(directory, `loaders`);
397
398 try {
399 if (fs.statSync(userLoaderDirectoryPath).isDirectory()) {
400 root.push(userLoaderDirectoryPath);
401 }
402 } catch (err) {
403 debug(`Error resolving user loaders directory`, err);
404 }
405
406 return {
407 modules: [...root, path.join(__dirname, `../loaders`), `node_modules`],
408 // Bare loaders should always be loaded via the user dependencies (loaders
409 // configured via third-party like gatsby use require.resolve)
410 plugins: [PnpWebpackPlugin.moduleLoader(`${directory}/`)]
411 };
412 }
413
414 const config = {
415 // Context is the base directory for resolving the entry option.
416 context: directory,
417 entry: getEntry(),
418 output: getOutput(),
419 module: getModule(),
420 plugins: getPlugins(),
421 // Certain "isomorphic" packages have different entry points for browser
422 // and server (see
423 // https://github.com/defunctzombie/package-browser-field-spec); setting
424 // the target tells webpack which file to include, ie. browser vs main.
425 target: stage === `build-html` || stage === `develop-html` ? `node` : `web`,
426 devtool: getDevtool(),
427 // Turn off performance hints as we (for now) don't want to show the normal
428 // webpack output anywhere.
429 performance: {
430 hints: false
431 },
432 mode: getMode(),
433 resolveLoader: getResolveLoader(),
434 resolve: getResolve(stage),
435 node: {
436 __filename: true
437 }
438 };
439
440 if (stage === `build-javascript`) {
441 const componentsCount = store.getState().components.size;
442 config.optimization = {
443 runtimeChunk: {
444 name: `webpack-runtime`
445 },
446 // use hashes instead of ids for module identifiers
447 // TODO update to deterministic in webpack 5 (hashed is deprecated)
448 // @see https://webpack.js.org/guides/caching/#module-identifiers
449 moduleIds: `hashed`,
450 splitChunks: {
451 name: false,
452 chunks: `all`,
453 cacheGroups: {
454 default: false,
455 vendors: false,
456 commons: {
457 name: `commons`,
458 chunks: `all`,
459 // if a chunk is used more than half the components count,
460 // we can assume it's pretty global
461 minChunks: componentsCount > 2 ? componentsCount * 0.5 : 2
462 },
463 react: {
464 name: `commons`,
465 chunks: `all`,
466 test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/
467 },
468 // Only create one CSS file to avoid
469 // problems with code-split CSS loading in different orders
470 // causing inconsistent/non-determanistic styling
471 // See https://github.com/gatsbyjs/gatsby/issues/11072
472 styles: {
473 name: `styles`,
474 // This should cover all our types of CSS.
475 test: /\.(css|scss|sass|less|styl)$/,
476 chunks: `all`,
477 enforce: true,
478 // this rule trumps all other rules because of the priority.
479 priority: 10
480 }
481 }
482 },
483 minimizer: [// TODO: maybe this option should be noMinimize?
484 !program.noUglify && plugins.minifyJs(), plugins.minifyCss()].filter(Boolean)
485 };
486 }
487
488 if (stage === `build-html` || stage === `develop-html`) {
489 // Packages we want to externalize to save some build time
490 // https://github.com/gatsbyjs/gatsby/pull/14208#pullrequestreview-240178728
491 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
492
493 const userExternalList = [`es6-promise`, `minimatch`, `pify`, `react-helmet`, `react`, /^react-dom\//];
494
495 const checkItem = (item, request) => {
496 if (typeof item === `string` && item === request) {
497 return true;
498 } else if (item instanceof RegExp && item.test(request)) {
499 return true;
500 }
501
502 return false;
503 };
504
505 const isExternal = request => {
506 if (externalList.some(item => checkItem(item, request))) {
507 return `umd ${require.resolve(request)}`;
508 }
509
510 if (userExternalList.some(item => checkItem(item, request))) {
511 return `umd ${request}`;
512 }
513
514 return null;
515 };
516
517 config.externals = [function (context, request, callback) {
518 const external = isExternal(request);
519
520 if (external !== null) {
521 callback(null, external);
522 } else {
523 callback();
524 }
525 }];
526 }
527
528 store.dispatch(actions.replaceWebpackConfig(config));
529
530 const getConfig = () => store.getState().webpack;
531
532 await apiRunnerNode(`onCreateWebpackConfig`, {
533 getConfig,
534 stage,
535 rules,
536 loaders,
537 plugins,
538 parentSpan
539 });
540 return getConfig();
541};
542//# sourceMappingURL=webpack.config.js.map
\No newline at end of file