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