UNPKG

21.8 kBJavaScriptView Raw
1/**
2 * Created by zuozhuo on 16/9/6.
3 */
4"use strict";
5const webpack = require('webpack');
6const path = require('path');
7const HtmlWebpackPlugin = require('html-webpack-plugin');
8const CircularDependencyPlugin = require('circular-dependency-plugin');
9const ExtractTextPlugin = require('extract-text-webpack-plugin');
10const InlineChunkWebpackPlugin = require('html-webpack-inline-chunk-plugin');
11const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
12// 因为1.x的webpack每次编译时,如果app模块内的代码改动比较大,整个module id都会变,导致vendor也变了
13const WebpackStableModuleIdAndHash = require('webpack-stable-module-id-and-hash');
14const GitRevisionPlugin = require('git-revision-webpack-plugin');
15const ChunkLoadingEventPlugin = require('./ChunkLoadingEventPlugin.js');
16const dateFns = require('date-fns');
17const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
18const autoprefixer = require('autoprefixer');
19const pxtorem = require('postcss-pxtorem');
20
21
22const gitRevisionPlugin = new GitRevisionPlugin();
23const lessExtract = new ExtractTextPlugin('less.[contenthash].css');
24const scssExtract = new ExtractTextPlugin('scss.[contenthash].css');
25const cssExtract = new ExtractTextPlugin('css.[contenthash].css');
26const cssModulesExtract = new ExtractTextPlugin('css-modules.[contenthash].css', {
27 // css modules中的css必须加这个才能extract出来
28 allChunks: true
29});
30
31const 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];
42const MY_AUTO_PREFIXER = autoprefixer({
43 browsers: BROWSERS_LIST
44});
45// https://www.npmjs.com/package/postcss-pxtorem#options
46const 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 */
64function createLoader(loaderName, option) {
65 if (option)
66 return `${loaderName}?${JSON.stringify(option)}`;
67 else
68 return loaderName;
69}
70
71// built-in
72const 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
125const 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];
140const 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
155function 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
500module.exports = webpackConfigMaker;
\No newline at end of file