1 | /**
|
2 | * Created by zuozhuo on 16/9/6.
|
3 | */
|
4 | ;
|
5 | const webpack = require('webpack');
|
6 | const path = require('path');
|
7 | const HtmlWebpackPlugin = require('html-webpack-plugin');
|
8 | const CircularDependencyPlugin = require('circular-dependency-plugin');
|
9 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
10 | const InlineChunkWebpackPlugin = require('html-webpack-inline-chunk-plugin');
|
11 | const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
|
12 | // 因为1.x的webpack每次编译时,如果app模块内的代码改动比较大,整个module id都会变,导致vendor也变了
|
13 | const WebpackStableModuleIdAndHash = require('webpack-stable-module-id-and-hash');
|
14 | const GitRevisionPlugin = require('git-revision-webpack-plugin');
|
15 | const ChunkLoadingEventPlugin = require('./ChunkLoadingEventPlugin.js');
|
16 | const dateFns = require('date-fns');
|
17 | const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
|
18 | const autoprefixer = require('autoprefixer');
|
19 | const pxtorem = require('postcss-pxtorem');
|
20 |
|
21 |
|
22 | const gitRevisionPlugin = new GitRevisionPlugin();
|
23 | const lessExtract = new ExtractTextPlugin('less.[contenthash].css');
|
24 | const scssExtract = new ExtractTextPlugin('scss.[contenthash].css');
|
25 | const cssExtract = new ExtractTextPlugin('css.[contenthash].css');
|
26 | const cssModulesExtract = new ExtractTextPlugin('css-modules.[contenthash].css', {
|
27 | // css modules中的css必须加这个才能extract出来
|
28 | allChunks: true
|
29 | });
|
30 |
|
31 | const BROWSERS_LIST = [
|
32 | 'ie >= 8',
|
33 | 'ie_mob >= 10',
|
34 | 'ff >= 30',
|
35 | 'chrome >= 34',
|
36 | 'safari >= 7',
|
37 | 'opera >= 23',
|
38 | 'ios >= 7',
|
39 | 'android >= 2.3',
|
40 | 'bb >= 10'
|
41 | ];
|
42 | const MY_AUTO_PREFIXER = autoprefixer({
|
43 | browsers: BROWSERS_LIST
|
44 | });
|
45 | // https://www.npmjs.com/package/postcss-pxtorem#options
|
46 | const MY_PX_TO_REM = pxtorem({
|
47 | rootValue: 16,
|
48 | unitPrecision: 5,
|
49 | selectorBlackList: [/^body$/, /^html$/, /^\.xm-footbar$/, /^\.xm-navbar$/], // 不进行转换的选择器
|
50 | propList: ['*'],
|
51 | replace: true,
|
52 | mediaQuery: false,
|
53 | minPixelValue: 0
|
54 | });
|
55 |
|
56 |
|
57 | /*
|
58 | - createLoader支持 -autoprefixer删除
|
59 | - joinLoaders
|
60 | - allLoaders
|
61 | - allLoaderPacks
|
62 | - 将所有loaders分离到外部文件夹
|
63 | */
|
64 | function createLoader(loaderName, option) {
|
65 | if (option)
|
66 | return `${loaderName}?${JSON.stringify(option)}`;
|
67 | else
|
68 | return loaderName;
|
69 | }
|
70 |
|
71 | // built-in
|
72 | const PRESET_LOADERS = {
|
73 | postCssLoader: (function () {
|
74 |
|
75 | return createLoader('postcss-loader', {
|
76 | sourceMap: true, // 只能设置 true | inline 不能设置false
|
77 | // plugins: [ ] // 这里不能使用plugin属性,请在后面的 postcss 属性配置
|
78 | // parser: 'postcss-scss' // 如果需要使用 precss,需要设置这个parser
|
79 | })
|
80 |
|
81 | })(),
|
82 | sassLoader: (function () {
|
83 | // 完整option请参考
|
84 | // https://github.com/sass/node-sass#options
|
85 | const option = {
|
86 | // includePaths: [path.resolve(__dirname, "../src/sass")]
|
87 | // TODO 这里有依赖,后面考虑拆分
|
88 | includePaths: [path.join(process.cwd(), "src/sass")]
|
89 | };
|
90 | return createLoader(require.resolve('sass-loader'), option);
|
91 | })(),
|
92 | babelLoader: (function () {
|
93 | const options = {
|
94 | presets: [
|
95 | [
|
96 | 'es2015',
|
97 | {loose: true}
|
98 | ],
|
99 | 'react',
|
100 | 'stage-0'
|
101 | ],
|
102 | plugins: [
|
103 | // 因为app.js里已经包含了 import './js/tool/polyfills' (包含 regenerator-runtime)
|
104 | // 'babel-plugin-transform-runtime', // 支持generator、promise、async、await
|
105 | "babel-plugin-dev-expression", // 替换 __DEV__ invariant warning
|
106 | "babel-plugin-add-module-exports", // 解决es6 export default问题
|
107 | 'babel-plugin-transform-decorators-legacy', // 支持装饰器
|
108 | "babel-plugin-transform-react-remove-prop-types", // 生产环境移除propTypes
|
109 | "babel-plugin-lodash", // 优化缩减lodash
|
110 | // 将所有文件里的新增的es6helper方法
|
111 | // (_inherits\_objectWithoutProperties\possibleConstructorReturn)提取到一个文件里,以便减少体积
|
112 | ["babel-plugin-transform-helper", {
|
113 | helperFilename: '_babelEs6TempHelper_.js'
|
114 | }]
|
115 | ]
|
116 | };
|
117 | return createLoader(require.resolve('babel-loader'), options)
|
118 | })(),
|
119 |
|
120 | };
|
121 |
|
122 |
|
123 | // TODO
|
124 | // - 增加sourcemap
|
125 | const myCssModulesLoaders = [
|
126 | //'style-loader',
|
127 | /*
|
128 | localIdentName 代表输出的class名:
|
129 | - path 样式文件路径
|
130 | - name 样式文件名
|
131 | - local class名
|
132 | - hash 哈希码
|
133 | 默认值为 "[hash:base64]"
|
134 | */
|
135 | 'css-loader?-autoprefixer&modules&importLoaders=1&localIdentName=[path]--[name]-[ext]__[local]-[hash:base64:5]',
|
136 | PRESET_LOADERS.postCssLoader,
|
137 | 'resolve-url-loader',
|
138 | PRESET_LOADERS.sassLoader,
|
139 | ];
|
140 | const myCssModulesProductionLoaders = [
|
141 | //'style-loader', // 这个作用是将css生成<style>标签,而不是生成一个外部引用的css文件
|
142 | /*
|
143 | 这里了有个非常大的坑,在webpack 1.x里有个bug,
|
144 | webpack.optimize.UglifyJsPlugin压缩时默认会对css进行 autoprefixer,
|
145 | 会将所有vendor prefix按照他的逻辑去处理,那么很多样式在老版浏览器,例如iOS8会异常(例如flex无法工作)
|
146 | 所以这里使用-autoprefixer取消css-loader自带的autoprefixer,手动将autoprefixer添加在后面的流程里
|
147 | 参考: https://github.com/webpack/webpack/issues/2543
|
148 | */
|
149 | 'css-loader?-autoprefixer&modules&importLoaders=1&localIdentName=[hash:base64]',
|
150 | PRESET_LOADERS.postCssLoader,
|
151 | 'resolve-url-loader',
|
152 | PRESET_LOADERS.sassLoader,
|
153 | ];
|
154 |
|
155 | function webpackConfigMaker(options) {
|
156 |
|
157 | const DEFAULT_OPTIONS = {
|
158 | https: false,
|
159 | sourceMap: false,
|
160 | debug: process.env.NODE_ENV !== 'production',
|
161 | appEntry: [],
|
162 | vendorEntry: [],
|
163 | commonChunks: [],
|
164 | publicPath: '/qn-dist/',
|
165 | distPath: path.join(process.cwd(), 'dist-prepare'), // 这里必须是绝对物理路径,否则编译会报错
|
166 | htmlTpl: 'src/index.html'
|
167 | };
|
168 | options = Object.assign(DEFAULT_OPTIONS, options);
|
169 | if (!path.isAbsolute(options.distPath)) {
|
170 | // 将distPath纠正为绝对物理路径,避免webpack报错
|
171 | options.distPath = path.join(process.cwd(), options.distPath);
|
172 | }
|
173 | const isProductionENV = !options.debug;
|
174 | const shouldBuildSourceMap = !isProductionENV || options.sourceMap;
|
175 |
|
176 | const HOST = '0.0.0.0';
|
177 | const PORT = process.env.PORT || 8080;
|
178 | const DEV_SERVER = `${options.https ? 'https' : 'http'}://${HOST}:${PORT}`;
|
179 | const DEFAULT_APP_ENTRY = isProductionENV ? [] : [
|
180 | `webpack-dev-server/client?${DEV_SERVER}`, // Automatic Refresh ,支持在浏览器console显示Refresh compile信息
|
181 |
|
182 | require.resolve('react-dev-utils/webpackHotDevClient'), // 使用增强的hotDevClient(带报错overlay)
|
183 | // "webpack/hot/dev-server", // 支持 hot reload + full page reload
|
184 | // // "webpack/hot/only-dev-server", // Hot Module Replacement 仅支持hot reload, full page reload会出warning
|
185 | ];
|
186 |
|
187 | const DEFAULT_VENDOR_ENTRY = [
|
188 | 'babel-regenerator-runtime', // 为了支持ES7的async和await,以及不支持Promise的机器polyfill
|
189 | ];
|
190 |
|
191 | let config = {
|
192 | node: {
|
193 | // 避免某些npm包(例如browserslist)的代码中使用了node的fs模块,
|
194 | // 但是在浏览器环境中是没有fs模块的,导致浏览器中抛错
|
195 | console: true,
|
196 | fs: 'empty',
|
197 | net: 'empty',
|
198 | tls: 'empty',
|
199 | },
|
200 | debug: !isProductionENV,
|
201 | entry: {
|
202 | app: DEFAULT_APP_ENTRY.concat(options.appEntry),
|
203 | vendors: DEFAULT_VENDOR_ENTRY.concat(options.vendorEntry)
|
204 | },
|
205 | devServer: { // 传递给webpack-dev-server的配置
|
206 | // overlay: true // 增加报错浮层
|
207 | },
|
208 | output: {
|
209 | path: options.distPath,
|
210 | publicPath: options.publicPath,
|
211 | filename: '[name].[chunkhash].js',
|
212 | // 不能是 [name].[chunkhash].js.map 否则sourcemap会不正确
|
213 | // 因为sourcemap不仅仅是js,还有css的。 [file]就相当于前面的filename
|
214 | sourceMapFilename: "[file].map",
|
215 | chunkFilename: "chunk.[name].[id].[chunkhash].js"
|
216 | },
|
217 | resolveLoader: {
|
218 | alias: {
|
219 | // 新增一个复制功能的copy-loader别名
|
220 | 'copy-loader': 'file-loader?name=[path][name].[ext]',
|
221 | 'my-css-with-sourcemap-loader': `css-loader?-autoprefixer&sourceMap!${PRESET_LOADERS.postCssLoader}`,
|
222 | 'my-css-loader': `css-loader?-autoprefixer!${PRESET_LOADERS.postCssLoader}`,
|
223 | }
|
224 | },
|
225 | // 提取到外部文件里去(postcss-plugins)
|
226 | // webpack1的postcss的plugin只能配在这里,不能和loader写在一起,因为所有plugin是函数,没法序列化
|
227 | postcss: () => {
|
228 | return [
|
229 | /* // 使用precss代替sass-loader编译scss(目前@import指令貌似有问题,暂时还是用sass-loader吧)
|
230 | precss({
|
231 | 'import':{ // postcss-partial-import的独立配置(namespace)
|
232 | // path:[path.join(process.cwd(), "src/sass")],
|
233 | extension:'.scss'
|
234 | }
|
235 | }),
|
236 | */
|
237 | MY_AUTO_PREFIXER,
|
238 | // MY_PX_TO_REM, // 因为secretWork项目里有很多以px为单位的sprite图,自动转为rem会有问题,所以暂时不弄了
|
239 | ];
|
240 | },
|
241 | module: {
|
242 | //loader的处理顺序为从后往前
|
243 | loaders: [
|
244 | /*
|
245 | {
|
246 | test: function test(filePath) {
|
247 | return (/\.less$/.test(filePath) && !/\.module\.less$/.test(filePath));
|
248 | }
|
249 | }
|
250 | * */
|
251 | {
|
252 | // 将require('xxxx.html')转为js内字符串变量
|
253 | test: function (filePath) {
|
254 | return (/\.html$/).test(filePath) && !(/\.external\.html$/).test(filePath);
|
255 | },
|
256 | loaders: [
|
257 | createLoader(require.resolve('html-loader'), {
|
258 | ignoreCustomFragments: [/\{\{.*?}}/],
|
259 | minimize: false,//开启html压缩后貌似编译报错,而且编译非常慢
|
260 | // root: path.resolve(__dirname, 'assets'),
|
261 | // 需要处理的图片和js\css的md5戳
|
262 | attrs: ['img:src', 'link:href', 'script:src']
|
263 | }),
|
264 | // createLoader('nunjucks-loader',{})
|
265 | ]
|
266 | },
|
267 | {
|
268 | // 将require('xxxx.external.html')
|
269 | // ==> 转为字符串 (html-loader)
|
270 | // ==> 转为外部文件 (extract-loader)
|
271 | // ==> 转为md5形式的文件url (file-loader)
|
272 | test: /\.external\.html$/,
|
273 | loaders: [
|
274 | createLoader(require.resolve('file-loader'), {
|
275 | name: '[path][name].[hash:8].[ext]'
|
276 | }),
|
277 | createLoader(require.resolve('extract-loader')),
|
278 | createLoader(require.resolve('html-loader'), {
|
279 | ignoreCustomFragments: [/\{\{.*?}}/],
|
280 | minimize: false,//开启html压缩后貌似编译报错,而且编译非常慢
|
281 | // root: path.resolve(__dirname, 'assets'),
|
282 | // 需要处理的图片和js\css的md5戳
|
283 | attrs: ['img:src', 'link:href', 'script:src']
|
284 | }),
|
285 | // createLoader('nunjucks-loader',{})
|
286 | ]
|
287 | },
|
288 | {
|
289 | // Autoprefixer 必须要这个玩意,所以请加上,否则会报错
|
290 | /*
|
291 | Module parse failed: /Users/zuozhuo/workspace/secretWork/node_modules/caniuse-db/region-usage-json/AD.json Unexpected token (1:5)
|
292 | You may need an appropriate loader to handle this file type.
|
293 | SyntaxError: Unexpected token (1:5)
|
294 | */
|
295 | test: /\.json$/,
|
296 | loaders: [
|
297 | require.resolve("json-loader"),
|
298 | ],
|
299 | },
|
300 | {
|
301 | test: /\.js$/,
|
302 | exclude: [
|
303 | // 排除非node_modules/xmui的node_modules
|
304 | // /node_modules\/(?!xmui)/
|
305 | /node_modules/
|
306 | ],
|
307 | // include: [],
|
308 | loaders: (function () {
|
309 | let _loaders = [
|
310 | PRESET_LOADERS.babelLoader
|
311 | ];
|
312 | // 开发环境增加hot-loader
|
313 | if (!isProductionENV) {
|
314 | // isProductionENV ? require.resolve('do-nothing-loader') : require.resolve('react-hot-loader'),
|
315 | // 貌似 do-nothing-loader 会导致 es6 的sourcemap丢失
|
316 | _loaders.unshift(require.resolve('react-hot-loader'));
|
317 | }
|
318 | return _loaders;
|
319 | })(),
|
320 | },
|
321 | {
|
322 | test: /\.less$/,
|
323 | // 将css从js里分离出来,并行下载加快渲染速度(并支持sourceMap)
|
324 | loader: lessExtract.extract(
|
325 | shouldBuildSourceMap
|
326 | ? `my-css-with-sourcemap-loader!less-loader?sourceMap`
|
327 | : `my-css-loader!less-loader`
|
328 | )
|
329 | },
|
330 | // 使用sass-loader来编译scss
|
331 | {
|
332 | test: function (filePath) {
|
333 | return (/\.scss$/).test(filePath) && !(/\.module\.scss$/).test(filePath);
|
334 | },
|
335 | loader: scssExtract.extract(
|
336 | shouldBuildSourceMap
|
337 | // 解决url(../)相对路径问题,不过貌似sourcemap就失效了
|
338 | // // !resolve-url-loader!sass-loader?sourceMap
|
339 | ? `my-css-with-sourcemap-loader!resolve-url-loader?sourceMap!sass-loader?sourceMap`
|
340 | : `my-css-loader!resolve-url-loader!sass-loader`
|
341 | )
|
342 | },
|
343 | // 使用postcss来编译scss(暂时有问题)
|
344 | {
|
345 | test: function (filePath) {
|
346 | return false;
|
347 | // return (/\.scss$/).test(filePath) && !(/\.module\.scss$/).test(filePath);
|
348 | },
|
349 | loader: scssExtract.extract(
|
350 | shouldBuildSourceMap
|
351 | // 解决url(../)相对路径问题,不过貌似sourcemap就失效了
|
352 | // // !resolve-url-loader!sass-loader?sourceMap
|
353 | ? `my-css-with-sourcemap-loader`
|
354 | : `my-css-loader`
|
355 | )
|
356 | },
|
357 | {
|
358 | test: /\.(scss-m|module\.scss)$/,
|
359 | loader: cssModulesExtract.extract(
|
360 | isProductionENV
|
361 | ? myCssModulesProductionLoaders.join('!')
|
362 | : myCssModulesLoaders.join('!')
|
363 | ),
|
364 | // loader: cssModulesExtract.extract(
|
365 | // myCssModulesLoaders.join('!')
|
366 | // )
|
367 | // loaders: myCssModulesLoaders
|
368 | },
|
369 | {
|
370 | test: /\.css$/,
|
371 | loader: cssExtract.extract(
|
372 | shouldBuildSourceMap
|
373 | ? `my-css-with-sourcemap-loader`
|
374 | : `my-css-loader`
|
375 | )
|
376 | },
|
377 | {
|
378 | // 如果图片小于1000(1kb),自动转为base64内嵌,否则生成[path][name].[hash:8].[ext]外部文件
|
379 | // url-loader已经包含了file-loader的功能,请不要另外再使用file-loader
|
380 | test: /\.(png|jpe?g|ico|otf|gif|svg|woff|ttf|eot)$/,
|
381 | loaders: [
|
382 | createLoader(require.resolve('url-loader'), {
|
383 | limit: 1000,
|
384 | name: '[path][name].[hash:8].[ext]'
|
385 | })
|
386 | ]
|
387 | }
|
388 | ]
|
389 | },
|
390 | plugins: [
|
391 | // 修改chunk加载事件,便于做loading指示器等
|
392 | new ChunkLoadingEventPlugin(),
|
393 |
|
394 | // yarn add circular-dependency-plugin
|
395 | new CircularDependencyPlugin({
|
396 | // exclude detection of files based on a RegExp
|
397 | exclude: /node_modules/, // /a\.js|node_modules/
|
398 | // add errors to webpack instead of warnings
|
399 | failOnError: false
|
400 | }),
|
401 |
|
402 | // 提取公共模块部分代码为独立chunk文件
|
403 | new webpack.optimize.CommonsChunkPlugin({
|
404 | names: [
|
405 | 'vendors', // 这里对应的就是entry里的 vendors
|
406 | 'manifest' //必须将manifest独立出来,否则会位于vendors中,导致vendors的md5永远在变化
|
407 | ].concat(options.commonChunks),
|
408 | // 注意: webpack-dev-server 模式下[chunkhash]不可用(vendors返回为undefined),请使用[hash].
|
409 | // webpack命令行直接build是可以用[chunkhash]的
|
410 | filename: isProductionENV ? "[name].[chunkhash].js" : "[name].[hash].js"
|
411 | }),
|
412 |
|
413 | // 模板传参数请看源码: HtmlWebpackPlugin.prototype.executeTemplate
|
414 | new HtmlWebpackPlugin({
|
415 | filename: 'index.html',
|
416 | template: options.htmlTpl,
|
417 | inject: 'body',
|
418 |
|
419 | //html压缩
|
420 | minify: isProductionENV ? {
|
421 | removeComments: true,
|
422 | collapseWhitespace: true
|
423 | } : false,
|
424 |
|
425 | // htmlWebpackPlugin.options增加属性
|
426 | // 用于记录当前git的代码版本和发布时间
|
427 | XM_GIT_COMMIT_HASH: gitRevisionPlugin.commithash(),
|
428 | XM_PUBLISH_DATE: dateFns.format(new Date(), 'YYYY/MM/DD HH:mm:ss'),
|
429 | }),
|
430 |
|
431 | new InlineChunkWebpackPlugin({
|
432 | // 将manifest的chunk在html中直出,减少http请求
|
433 | inlineChunks: ['manifest']
|
434 | }),
|
435 |
|
436 | // 优化React的生产代码,用来生成没有注释和各种warning及仅debug相关的代码
|
437 | new webpack.DefinePlugin({
|
438 | // 声明全局变量: process.env.NODE_ENV
|
439 | 'process.env': {
|
440 | NODE_ENV: JSON.stringify(isProductionENV ? "production" : "development")
|
441 | }
|
442 | }),
|
443 |
|
444 | new FriendlyErrorsWebpackPlugin(),
|
445 |
|
446 | lessExtract,
|
447 | scssExtract,
|
448 | cssExtract,
|
449 | cssModulesExtract,
|
450 | ]
|
451 | };
|
452 |
|
453 | // 生产环境的特殊构建
|
454 | if (isProductionENV) {
|
455 | config.plugins = config.plugins.concat([
|
456 | // 缩减lodash的体积 TODO 貌似有问题,导致lodash报错
|
457 | // new LodashModuleReplacementPlugin(),
|
458 | // 优化模块id顺序,可能与WebpackStableModuleIdAndHash冲突
|
459 | // new webpack.optimize.OccurrenceOrderPlugin(),
|
460 | // new webpack.NamedModulesPlugin(),
|
461 | new webpack.optimize.DedupePlugin(), // 去除重复模块 有些JS库有自己的依赖树,并且这些库可能有交叉的依赖,DedupePlugin可以找出他们并删除重复的依赖。
|
462 | new webpack.optimize.UglifyJsPlugin({ // 压缩JS
|
463 | output: {
|
464 | comments: false // remove all comments
|
465 | },
|
466 | compress: {
|
467 | warnings: false
|
468 | }
|
469 | }),
|
470 | // 稳定模块id(当app.js大面积修改时,可能导致vendor里的模块id变化,影响缓存)
|
471 | new WebpackStableModuleIdAndHash()
|
472 | ])
|
473 | }
|
474 | // 开发环境的特殊构建
|
475 | else {
|
476 | config.plugins = config.plugins.concat([
|
477 | // 参考 https://github.com/gaearon/react-hot-boilerplate
|
478 | new webpack.HotModuleReplacementPlugin(),
|
479 | ]);
|
480 | }
|
481 |
|
482 | if (shouldBuildSourceMap) {
|
483 | Object.assign(config, {
|
484 | //开启js的sourcemap功能支持
|
485 | // must be 'source-map' or 'inline-source-map'
|
486 | // 详解sourcemap配置: https://segmentfault.com/a/1190000004280859
|
487 | devtool: isProductionENV
|
488 | ? 'source-map' // 生产环境因为有压缩代码,所以必须是source-map保证完整的源码
|
489 | : 'cheap-module-source-map' // 开发环境使用这个cheap-module-source-map就行了,编译速度比source-map更快
|
490 | });
|
491 | }
|
492 |
|
493 | console.log('webpack配置:');
|
494 | console.log(JSON.stringify(config, null, 4));
|
495 | return config;
|
496 |
|
497 |
|
498 | }
|
499 |
|
500 | module.exports = webpackConfigMaker; |
\ | No newline at end of file |