1 | import webpack from 'webpack';
|
2 | import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
|
3 | import SystemBellWebpackPlugin from 'system-bell-webpack-plugin';
|
4 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
|
5 | import ManifestPlugin from 'webpack-manifest-plugin';
|
6 | import SWPrecacheWebpackPlugin from 'sw-precache-webpack-plugin';
|
7 | import autoprefixer from 'autoprefixer';
|
8 | import { dirname, resolve, join, extname } from 'path';
|
9 | import { existsSync } from 'fs';
|
10 | import eslintFormatter from 'react-dev-utils/eslintFormatter';
|
11 | import assert from 'assert';
|
12 | import deprecate from 'deprecate';
|
13 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
14 | import CopyWebpackPlugin from 'copy-webpack-plugin';
|
15 | import HTMLWebpackPlugin from 'html-webpack-plugin';
|
16 | import ProgressPlugin from 'progress-bar-webpack-plugin';
|
17 | import { sync as resolveSync } from 'resolve';
|
18 | import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
19 | import HardSourceWebpackPlugin from 'hard-source-webpack-plugin';
|
20 | import uglifyJSConfig from './defaultConfigs/uglifyJS';
|
21 | import babelConfig from './defaultConfigs/babel';
|
22 | import defaultBrowsers from './defaultConfigs/browsers';
|
23 | import stringifyObject from './stringifyObject';
|
24 | import normalizeTheme from './normalizeTheme';
|
25 | import { applyWebpackConfig } from './applyWebpackConfig';
|
26 | import readRc from './readRc';
|
27 | import { stripLastSlash } from './utils';
|
28 | import { getPkgPath, shouldTransform } from './es5ImcompatibleVersions';
|
29 |
|
30 | const { TsConfigPathsPlugin } = require('awesome-typescript-loader');
|
31 | const debug = require('debug')('af-webpack:getConfig');
|
32 |
|
33 | if (process.env.DISABLE_TSLINT) {
|
34 | deprecate('DISABLE_TSLINT is deprecated, use TSLINT=none instead');
|
35 | }
|
36 | if (process.env.DISABLE_ESLINT) {
|
37 | deprecate('DISABLE_ESLINT is deprecated, use ESLINT=none instead');
|
38 | }
|
39 | if (process.env.NO_COMPRESS) {
|
40 | deprecate('NO_COMPRESS is deprecated, use COMPRESS=none instead');
|
41 | }
|
42 |
|
43 | export default function getConfig(opts = {}) {
|
44 | assert(opts.cwd, 'opts.cwd must be specified');
|
45 |
|
46 | const isDev = process.env.NODE_ENV === 'development';
|
47 | const theme = normalizeTheme(opts.theme);
|
48 | const postcssOptions = {
|
49 |
|
50 |
|
51 | ident: 'postcss',
|
52 | plugins: () => [
|
53 | require('postcss-flexbugs-fixes'),
|
54 | autoprefixer({
|
55 | browsers: opts.browserslist || defaultBrowsers,
|
56 | flexbox: 'no-2009',
|
57 | }),
|
58 | ...(opts.extraPostCSSPlugins ? opts.extraPostCSSPlugins : []),
|
59 | ],
|
60 | };
|
61 | const cssModulesConfig = {
|
62 | modules: true,
|
63 | localIdentName: isDev
|
64 | ? '[name]__[local]___[hash:base64:5]'
|
65 | : '[local]___[hash:base64:5]',
|
66 | };
|
67 | const lessOptions = {
|
68 | modifyVars: theme,
|
69 | ...(opts.lessLoaderOptions || {}),
|
70 | };
|
71 | const cssOptions = {
|
72 | importLoaders: 1,
|
73 | ...(isDev
|
74 | ? {}
|
75 | : {
|
76 | minimize: !(
|
77 | process.env.CSS_COMPRESS === 'none' ||
|
78 | process.env.COMPRESS === 'none' ||
|
79 | process.env.NO_COMPRESS
|
80 | )
|
81 | ? {
|
82 |
|
83 | minifyFontValues: false,
|
84 | }
|
85 | : false,
|
86 | sourceMap: !opts.disableCSSSourceMap,
|
87 | }),
|
88 | ...(opts.cssLoaderOptions || {}),
|
89 | };
|
90 |
|
91 | function getCSSLoader(opts = {}) {
|
92 | const { cssModules, less, sass, sassOptions } = opts;
|
93 |
|
94 | let hasSassLoader = true;
|
95 | try {
|
96 | require.resolve('sass-loader');
|
97 | } catch (e) {
|
98 | hasSassLoader = false;
|
99 | }
|
100 |
|
101 | return [
|
102 | require.resolve('style-loader'),
|
103 | {
|
104 | loader: require.resolve('css-loader'),
|
105 | options: {
|
106 | ...cssOptions,
|
107 | ...(cssModules ? cssModulesConfig : {}),
|
108 | },
|
109 | },
|
110 | {
|
111 | loader: require.resolve('postcss-loader'),
|
112 | options: postcssOptions,
|
113 | },
|
114 | ...(less
|
115 | ? [
|
116 | {
|
117 | loader: require.resolve('less-loader'),
|
118 | options: lessOptions,
|
119 | },
|
120 | ]
|
121 | : []),
|
122 | ...(sass && hasSassLoader
|
123 | ? [
|
124 | {
|
125 | loader: require.resolve('sass-loader'),
|
126 | options: sassOptions,
|
127 | },
|
128 | ]
|
129 | : []),
|
130 | ];
|
131 | }
|
132 |
|
133 | function exclude(filePath) {
|
134 | if (/node_modules/.test(filePath)) {
|
135 | return true;
|
136 | }
|
137 | if (opts.cssModulesWithAffix) {
|
138 | if (/\.module\.(css|less|sass|scss)$/.test(filePath)) return true;
|
139 | }
|
140 | if (opts.cssModulesExcludes) {
|
141 | for (const exclude of opts.cssModulesExcludes) {
|
142 | if (filePath.indexOf(exclude) > -1) return true;
|
143 | }
|
144 | }
|
145 | }
|
146 |
|
147 | const cssRules = [
|
148 | ...(opts.cssModulesExcludes
|
149 | ? opts.cssModulesExcludes.map(file => {
|
150 | return {
|
151 | test(filePath) {
|
152 | return filePath.indexOf(file) > -1;
|
153 | },
|
154 | use: getCSSLoader({
|
155 | less: extname(file).toLowerCase() === '.less',
|
156 | sass:
|
157 | extname(file).toLowerCase() === '.sass' ||
|
158 | extname(file).toLowerCase() === '.scss',
|
159 | sassOptions: opts.sass,
|
160 | }),
|
161 | };
|
162 | })
|
163 | : []),
|
164 | ...(opts.cssModulesWithAffix
|
165 | ? [
|
166 | {
|
167 | test: /\.module\.css$/,
|
168 | use: getCSSLoader({
|
169 | cssModules: true,
|
170 | }),
|
171 | },
|
172 | {
|
173 | test: /\.module\.less$/,
|
174 | use: getCSSLoader({
|
175 | cssModules: true,
|
176 | less: true,
|
177 | }),
|
178 | },
|
179 | {
|
180 | test: /\.module\.(sass|scss)$/,
|
181 | use: getCSSLoader({
|
182 | cssModules: true,
|
183 | sass: true,
|
184 | sassOptions: opts.sass,
|
185 | }),
|
186 | },
|
187 | ]
|
188 | : []),
|
189 | {
|
190 | test: /\.css$/,
|
191 | exclude,
|
192 | use: getCSSLoader({
|
193 | cssModules: !opts.disableCSSModules,
|
194 | }),
|
195 | },
|
196 | {
|
197 | test: /\.css$/,
|
198 | include: /node_modules/,
|
199 | use: getCSSLoader(),
|
200 | },
|
201 | {
|
202 | test: /\.less$/,
|
203 | exclude,
|
204 | use: getCSSLoader({
|
205 | cssModules: !opts.disableCSSModules,
|
206 | less: true,
|
207 | }),
|
208 | },
|
209 | {
|
210 | test: /\.less$/,
|
211 | include: /node_modules/,
|
212 | use: getCSSLoader({
|
213 | less: true,
|
214 | }),
|
215 | },
|
216 | {
|
217 | test: /\.(sass|scss)$/,
|
218 | exclude,
|
219 | use: getCSSLoader({
|
220 | cssModules: !opts.disableCSSModules,
|
221 | sass: true,
|
222 | sassOptions: opts.sass,
|
223 | }),
|
224 | },
|
225 | {
|
226 | test: /\.(sass|scss)$/,
|
227 | include: /node_modules/,
|
228 | use: getCSSLoader({
|
229 | sass: true,
|
230 | sassOptions: opts.sass,
|
231 | }),
|
232 | },
|
233 | ];
|
234 |
|
235 |
|
236 | if (!isDev) {
|
237 | cssRules.forEach(rule => {
|
238 | rule.use = ExtractTextPlugin.extract({
|
239 | use: rule.use.slice(1),
|
240 | });
|
241 | });
|
242 | }
|
243 |
|
244 |
|
245 | const commonsPlugins = (opts.commons || []).map(common => {
|
246 | return new webpack.optimize.CommonsChunkPlugin(common);
|
247 | });
|
248 |
|
249 |
|
250 | const outputPath = opts.outputPath
|
251 | ? resolve(opts.cwd, opts.outputPath)
|
252 | : resolve(opts.cwd, 'dist');
|
253 |
|
254 |
|
255 | const copyPlugins = opts.copy ? [new CopyWebpackPlugin(opts.copy)] : [];
|
256 | if (existsSync(resolve(opts.cwd, 'public'))) {
|
257 | copyPlugins.push(
|
258 | new CopyWebpackPlugin(
|
259 | [
|
260 | {
|
261 | from: resolve(opts.cwd, 'public'),
|
262 | to: outputPath,
|
263 | toType: 'dir',
|
264 | },
|
265 | ],
|
266 | { ignore: ['**/.*'] },
|
267 | ),
|
268 | );
|
269 | }
|
270 |
|
271 |
|
272 | const jsHash = !isDev && opts.hash ? '.[chunkhash:8]' : '';
|
273 | const cssHash = !isDev && opts.hash ? '.[contenthash:8]' : '';
|
274 |
|
275 | const babelOptions = {
|
276 | ...(opts.babel || babelConfig),
|
277 | cacheDirectory: process.env.BABEL_CACHE !== 'none',
|
278 | babelrc: !!process.env.BABELRC,
|
279 | };
|
280 | babelOptions.plugins = [
|
281 | ...(babelOptions.plugins || []),
|
282 | ...(opts.disableDynamicImport
|
283 | ? [require.resolve('babel-plugin-dynamic-import-node-sync')]
|
284 | : []),
|
285 | ];
|
286 | const babelUse = [
|
287 | {
|
288 | loader: require('path').join(__dirname, 'debugLoader.js'),
|
289 | },
|
290 | {
|
291 | loader: require.resolve('babel-loader'),
|
292 | options: babelOptions,
|
293 | },
|
294 | ];
|
295 | const babelOptionsDeps = {
|
296 | presets: [
|
297 | [
|
298 | require.resolve('babel-preset-umi'),
|
299 | {
|
300 | disableTransform: true,
|
301 | },
|
302 | ],
|
303 | ],
|
304 | cacheDirectory: process.env.BABEL_CACHE !== 'none',
|
305 | babelrc: !!process.env.BABELRC,
|
306 | };
|
307 | const babelUseDeps = [
|
308 | {
|
309 | loader: require('path').join(__dirname, 'debugLoader.js'),
|
310 | },
|
311 | {
|
312 | loader: require.resolve('babel-loader'),
|
313 | options: babelOptionsDeps,
|
314 | },
|
315 | ];
|
316 |
|
317 | const eslintOptions = {
|
318 | formatter: eslintFormatter,
|
319 | baseConfig: {
|
320 | extends: [require.resolve('eslint-config-umi')],
|
321 | },
|
322 | ignore: false,
|
323 | eslintPath: require.resolve('eslint'),
|
324 | useEslintrc: false,
|
325 | };
|
326 |
|
327 |
|
328 | try {
|
329 | const { dependencies, devDependencies } = require(resolve('package.json'));
|
330 | if (dependencies.eslint || devDependencies.eslint) {
|
331 | const eslintPath = resolveSync('eslint', {
|
332 | basedir: opts.cwd,
|
333 | });
|
334 | eslintOptions.eslintPath = eslintPath;
|
335 | debug(`use user's eslint bin: ${eslintPath}`);
|
336 | }
|
337 | } catch (e) {
|
338 |
|
339 | }
|
340 |
|
341 |
|
342 | const userEslintRulePath = resolve(opts.cwd, '.eslintrc');
|
343 | if (existsSync(userEslintRulePath)) {
|
344 | try {
|
345 | const userRc = readRc(userEslintRulePath);
|
346 | debug(`userRc: ${JSON.stringify(userRc)}`);
|
347 | if (userRc.extends) {
|
348 | debug(`use user's .eslintrc: ${userEslintRulePath}`);
|
349 | eslintOptions.useEslintrc = true;
|
350 | eslintOptions.baseConfig = false;
|
351 | eslintOptions.ignore = true;
|
352 | } else {
|
353 | debug(`extend with user's .eslintrc: ${userEslintRulePath}`);
|
354 | eslintOptions.baseConfig = {
|
355 | ...eslintOptions.baseConfig,
|
356 | ...userRc,
|
357 | };
|
358 | }
|
359 | } catch (e) {
|
360 | debug(e);
|
361 | }
|
362 | }
|
363 |
|
364 | const extraBabelIncludes = opts.extraBabelIncludes || [];
|
365 | if (opts.es5ImcompatibleVersions) {
|
366 | extraBabelIncludes.push(a => {
|
367 | if (a.indexOf('node_modules') === -1) return false;
|
368 | const pkgPath = getPkgPath(a);
|
369 | return shouldTransform(pkgPath);
|
370 | });
|
371 | }
|
372 |
|
373 | const config = {
|
374 | bail: !isDev,
|
375 | devtool: opts.devtool || undefined,
|
376 | entry: opts.entry || null,
|
377 | output: {
|
378 | path: outputPath,
|
379 |
|
380 | pathinfo: isDev,
|
381 | filename: `[name]${jsHash}.js`,
|
382 | publicPath: opts.publicPath || undefined,
|
383 | chunkFilename: `[name]${jsHash}.async.js`,
|
384 | },
|
385 | resolve: {
|
386 | modules: [
|
387 | 'node_modules',
|
388 | resolve(__dirname, '../node_modules'),
|
389 | ...(opts.extraResolveModules || []),
|
390 | ],
|
391 | extensions: [
|
392 | ...(opts.extraResolveExtensions || []),
|
393 | '.web.js',
|
394 | '.web.jsx',
|
395 | '.web.ts',
|
396 | '.web.tsx',
|
397 | '.js',
|
398 | '.json',
|
399 | '.jsx',
|
400 | '.ts',
|
401 | '.tsx',
|
402 | ],
|
403 | alias: {
|
404 | ...opts.alias,
|
405 | },
|
406 | plugins:
|
407 | process.env.TS_CONFIG_PATHS_PLUGIN &&
|
408 | process.env.TS_CONFIG_PATHS_PLUGIN !== 'none'
|
409 | ? [new TsConfigPathsPlugin()]
|
410 | : [],
|
411 | },
|
412 | module: {
|
413 | rules: [
|
414 | ...(process.env.DISABLE_TSLINT || process.env.TSLINT === 'none'
|
415 | ? []
|
416 | : [
|
417 | {
|
418 | test: /\.tsx?$/,
|
419 | include: opts.cwd,
|
420 | exclude: /node_modules/,
|
421 | enforce: 'pre',
|
422 | use: [
|
423 | {
|
424 | options: {
|
425 | emitErrors: true,
|
426 |
|
427 | },
|
428 | loader: require.resolve('tslint-loader'),
|
429 | },
|
430 | ],
|
431 | },
|
432 | ]),
|
433 | ...(process.env.DISABLE_ESLINT || process.env.ESLINT === 'none'
|
434 | ? []
|
435 | : [
|
436 | {
|
437 | test: /\.(js|jsx)$/,
|
438 | include: opts.cwd,
|
439 | exclude: /node_modules/,
|
440 | enforce: 'pre',
|
441 | use: [
|
442 | {
|
443 | options: eslintOptions,
|
444 | loader: require.resolve('eslint-loader'),
|
445 | },
|
446 | ],
|
447 | },
|
448 | ]),
|
449 | {
|
450 | exclude: [
|
451 | /\.(html|ejs)$/,
|
452 | /\.json$/,
|
453 | /\.(js|jsx|ts|tsx)$/,
|
454 | /\.(css|less|scss|sass)$/,
|
455 | ...(opts.urlLoaderExcludes || []),
|
456 | ],
|
457 | loader: require.resolve('url-loader'),
|
458 | options: {
|
459 | limit: 10000,
|
460 | name: 'static/[name].[hash:8].[ext]',
|
461 | },
|
462 | },
|
463 | {
|
464 | test: /\.js$/,
|
465 | include: opts.cwd,
|
466 | exclude: /node_modules/,
|
467 | use: babelUse,
|
468 | },
|
469 | {
|
470 | test: /\.jsx$/,
|
471 | include: opts.cwd,
|
472 | use: babelUse,
|
473 | },
|
474 | {
|
475 | test: /\.(ts|tsx)$/,
|
476 | include: opts.cwd,
|
477 | exclude: /node_modules/,
|
478 | use: [
|
479 | ...babelUse,
|
480 | {
|
481 | loader: require.resolve('awesome-typescript-loader'),
|
482 | options: {
|
483 | configFileName:
|
484 | opts.tsConfigFile || join(opts.cwd, 'tsconfig.json'),
|
485 | transpileOnly: true,
|
486 | ...(opts.typescript || {}),
|
487 | },
|
488 | },
|
489 | ],
|
490 | },
|
491 | ...extraBabelIncludes.map(include => {
|
492 | return {
|
493 | test: /\.(js|jsx)$/,
|
494 | include:
|
495 | typeof include === 'string' ? join(opts.cwd, include) : include,
|
496 | use: babelUseDeps,
|
497 | };
|
498 | }),
|
499 | {
|
500 | test: /\.html$/,
|
501 | loader: require.resolve('file-loader'),
|
502 | options: {
|
503 | name: '[name].[ext]',
|
504 | },
|
505 | },
|
506 | ...cssRules,
|
507 | ],
|
508 | },
|
509 | plugins: [
|
510 | ...(isDev
|
511 | ? [
|
512 | new webpack.HotModuleReplacementPlugin(),
|
513 |
|
514 |
|
515 | new SystemBellWebpackPlugin(),
|
516 | ...(process.env.HARD_SOURCE && process.env.HARD_SOURCE !== 'none'
|
517 | ? [new HardSourceWebpackPlugin()]
|
518 | : []),
|
519 | ].concat(
|
520 | opts.devtool
|
521 | ? []
|
522 | : [
|
523 | new webpack.SourceMapDevToolPlugin({
|
524 | columns: false,
|
525 | moduleFilenameTemplate: info => {
|
526 | if (
|
527 | /\/koi-pkgs\/packages/.test(
|
528 | info.absoluteResourcePath,
|
529 | ) ||
|
530 | /packages\/koi-core/.test(info.absoluteResourcePath) ||
|
531 | /webpack\/bootstrap/.test(info.absoluteResourcePath) ||
|
532 | /\/node_modules\
|
533 | ) {
|
534 | return `internal:///${info.absoluteResourcePath}`;
|
535 | }
|
536 | return resolve(info.absoluteResourcePath).replace(
|
537 | /\\/g,
|
538 | '/',
|
539 | );
|
540 | },
|
541 | }),
|
542 | ],
|
543 | )
|
544 | : [
|
545 | ...(process.env.__FROM_TEST
|
546 | ? []
|
547 | : [new webpack.HashedModuleIdsPlugin()]),
|
548 | new webpack.optimize.ModuleConcatenationPlugin(),
|
549 | new ExtractTextPlugin({
|
550 | filename: `[name]${cssHash}.css`,
|
551 | allChunks: true,
|
552 | }),
|
553 | ...(opts.serviceworker
|
554 | ? [
|
555 | new SWPrecacheWebpackPlugin({
|
556 | filename: 'service-worker.js',
|
557 | minify: !(
|
558 | process.env.NO_COMPRESS || process.env.COMPRESS === 'none'
|
559 | ),
|
560 | staticFileGlobsIgnorePatterns: [
|
561 | /\.map$/,
|
562 | /asset-manifest\.json$/,
|
563 | ],
|
564 | ...opts.serviceworker,
|
565 | }),
|
566 | ]
|
567 | : []),
|
568 | ...(opts.manifest
|
569 | ? [
|
570 | new ManifestPlugin({
|
571 | fileName: 'manifest.json',
|
572 | ...opts.manifest,
|
573 | }),
|
574 | ]
|
575 | : []),
|
576 | ]),
|
577 | ...(isDev || (process.env.NO_COMPRESS || process.env.COMPRESS === 'none')
|
578 | ? []
|
579 | : [
|
580 | new webpack.optimize.UglifyJsPlugin({
|
581 | ...uglifyJSConfig,
|
582 | ...(opts.devtool ? { sourceMap: true } : {}),
|
583 | }),
|
584 | ]),
|
585 | new webpack.DefinePlugin({
|
586 | 'process.env.NODE_ENV': JSON.stringify(
|
587 |
|
588 | isDev ? 'development' : 'production',
|
589 | ),
|
590 | 'process.env.HMR': process.env.HMR,
|
591 |
|
592 | ...(process.env.SOCKET_SERVER
|
593 | ? {
|
594 | 'process.env.SOCKET_SERVER': JSON.stringify(
|
595 | process.env.SOCKET_SERVER,
|
596 | ),
|
597 | }
|
598 | : {}),
|
599 | ...stringifyObject(opts.define || {}),
|
600 | }),
|
601 | ...(opts.html ? [new HTMLWebpackPlugin(opts.html)] : []),
|
602 | new CaseSensitivePathsPlugin(),
|
603 | new webpack.LoaderOptionsPlugin({
|
604 | options: {
|
605 | context: __dirname,
|
606 | },
|
607 | }),
|
608 | new ProgressPlugin(),
|
609 | ...(process.env.TS_TYPECHECK ? [new ForkTsCheckerWebpackPlugin()] : []),
|
610 | ...(opts.ignoreMomentLocale
|
611 | ? [new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)]
|
612 | : []),
|
613 | ...commonsPlugins,
|
614 | ...copyPlugins,
|
615 | ...(process.env.ANALYZE
|
616 | ? [
|
617 | new BundleAnalyzerPlugin({
|
618 | analyzerMode: 'server',
|
619 | analyzerPort: process.env.ANALYZE_PORT || 8888,
|
620 | openAnalyzer: true,
|
621 | }),
|
622 | ]
|
623 | : []),
|
624 | ],
|
625 | externals: opts.externals,
|
626 | node: {
|
627 | dgram: 'empty',
|
628 | fs: 'empty',
|
629 | net: 'empty',
|
630 | tls: 'empty',
|
631 | child_process: 'empty',
|
632 | },
|
633 | performance: isDev
|
634 | ? {
|
635 | hints: false,
|
636 | }
|
637 | : {},
|
638 | };
|
639 |
|
640 | if (process.env.PUBLIC_PATH) {
|
641 | config.output.publicPath = `${stripLastSlash(process.env.PUBLIC_PATH)}/`;
|
642 | }
|
643 |
|
644 | return applyWebpackConfig(opts.cwd, config);
|
645 | }
|