UNPKG

20.4 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');
19
20const gitRevisionPlugin = new GitRevisionPlugin();
21const lessExtract = new ExtractTextPlugin('less.[contenthash].css');
22const scssExtract = new ExtractTextPlugin('scss.[contenthash].css');
23const cssExtract = new ExtractTextPlugin('css.[contenthash].css');
24const cssModulesExtract = new ExtractTextPlugin('css-modules.[contenthash].css', {
25 // css modules中的css必须加这个才能extract出来
26 allChunks: true
27});
28
29const 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];
40const MY_AUTO_PREFIXER = autoprefixer({
41 browsers: BROWSERS_LIST
42});
43
44
45/*
46 - createLoader支持 -autoprefixer删除
47 - joinLoaders
48 - allLoaders
49 - allLoaderPacks
50 - 将所有loaders分离到外部文件夹
51 */
52function createLoader(loaderName, option) {
53 if (option)
54 return `${loaderName}?${JSON.stringify(option)}`;
55 else
56 return loaderName;
57}
58
59// built-in
60const 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
113const 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];
128const 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
143function 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: [
292 isProductionENV ? require.resolve('do-nothing-loader') : require.resolve('react-hot-loader'),
293 PRESET_LOADERS.babelLoader
294 ],
295 },
296 {
297 test: /\.less$/,
298 // 将css从js里分离出来,并行下载加快渲染速度(并支持sourceMap)
299 loader: lessExtract.extract(
300 shouldBuildSourceMap
301 ? `my-css-with-sourcemap-loader!less-loader?sourceMap`
302 : `my-css-loader!less-loader`
303 )
304 },
305 // 使用sass-loader来编译scss
306 {
307 test: function (filePath) {
308 return (/\.scss$/).test(filePath) && !(/\.module\.scss$/).test(filePath);
309 },
310 loader: scssExtract.extract(
311 shouldBuildSourceMap
312 // 解决url(../)相对路径问题,不过貌似sourcemap就失效了
313 // // !resolve-url-loader!sass-loader?sourceMap
314 ? `my-css-with-sourcemap-loader!resolve-url-loader?sourceMap!sass-loader?sourceMap`
315 : `my-css-loader!resolve-url-loader!sass-loader`
316 )
317 },
318 // 使用postcss来编译scss(暂时有问题)
319 {
320 test: function (filePath) {
321 return false;
322 // return (/\.scss$/).test(filePath) && !(/\.module\.scss$/).test(filePath);
323 },
324 loader: scssExtract.extract(
325 shouldBuildSourceMap
326 // 解决url(../)相对路径问题,不过貌似sourcemap就失效了
327 // // !resolve-url-loader!sass-loader?sourceMap
328 ? `my-css-with-sourcemap-loader`
329 : `my-css-loader`
330 )
331 },
332 {
333 test: /\.(scss-m|module\.scss)$/,
334 loader: cssModulesExtract.extract(
335 isProductionENV
336 ? myCssModulesProductionLoaders.join('!')
337 : myCssModulesLoaders.join('!')
338 ),
339 // loader: cssModulesExtract.extract(
340 // myCssModulesLoaders.join('!')
341 // )
342 // loaders: myCssModulesLoaders
343 },
344 {
345 test: /\.css$/,
346 loader: cssExtract.extract(
347 shouldBuildSourceMap
348 ? `my-css-with-sourcemap-loader`
349 : `my-css-loader`
350 )
351 },
352 {
353 // 如果图片小于1000(1kb),自动转为base64内嵌,否则生成[path][name].[hash:8].[ext]外部文件
354 // url-loader已经包含了file-loader的功能,请不要另外再使用file-loader
355 test: /\.(png|jpe?g|ico|otf|gif|svg|woff|ttf|eot)$/,
356 loaders: [
357 createLoader(require.resolve('url-loader'), {
358 limit: 1000,
359 name: '[path][name].[hash:8].[ext]'
360 })
361 ]
362 }
363 ]
364 },
365 plugins: [
366 // 修改chunk加载事件,便于做loading指示器等
367 new ChunkLoadingEventPlugin(),
368
369 // yarn add circular-dependency-plugin
370 new CircularDependencyPlugin({
371 // exclude detection of files based on a RegExp
372 exclude: /node_modules/, // /a\.js|node_modules/
373 // add errors to webpack instead of warnings
374 failOnError: false
375 }),
376
377 new webpack.optimize.CommonsChunkPlugin({
378 names: [
379 'vendors', // 这里对应的就是entry里的 vendors
380 'manifest' //必须将manifest独立出来,否则会位于vendors中,导致vendors的md5永远在变化
381 ].concat(options.commonChunks),
382 // 注意: webpack-dev-server 模式下[chunkhash]不可用(vendors返回为undefined),请使用[hash].
383 // webpack直接build是可以用[chunkhash]的
384 filename: isProductionENV ? "[name].[chunkhash].js" : "[name].[hash].js"
385 }),
386
387 // 模板传参数请看源码: HtmlWebpackPlugin.prototype.executeTemplate
388 new HtmlWebpackPlugin({
389 filename: 'index.html',
390 template: options.htmlTpl,
391 inject: 'body',
392
393 //html压缩
394 minify: isProductionENV ? {
395 removeComments: true,
396 collapseWhitespace: true
397 } : false,
398
399 // htmlWebpackPlugin.options增加属性
400 // 用于记录当前git的代码版本和发布时间
401 XM_GIT_COMMIT_HASH: gitRevisionPlugin.commithash(),
402 XM_PUBLISH_DATE: dateFns.format(new Date(), 'YYYY/MM/DD HH:mm:ss'),
403 }),
404
405 new InlineChunkWebpackPlugin({
406 // 将manifest的chunk在html中直出,减少http请求
407 inlineChunks: ['manifest']
408 }),
409
410 // 优化React的生产代码,用来生成没有注释和各种warning及仅debug相关的代码
411 new webpack.DefinePlugin({
412 // 声明全局变量: process.env.NODE_ENV
413 'process.env': {
414 NODE_ENV: JSON.stringify(isProductionENV ? "production" : "development")
415 }
416 }),
417
418 new FriendlyErrorsWebpackPlugin(),
419
420 lessExtract,
421 scssExtract,
422 cssExtract,
423 cssModulesExtract,
424 ]
425 };
426
427 // 生产环境的特殊构建
428 if (isProductionENV) {
429 config.plugins = config.plugins.concat([
430 // 缩减lodash的体积 TODO 貌似有问题,导致lodash报错
431 // new LodashModuleReplacementPlugin(),
432 // 优化模块id顺序,可能与WebpackStableModuleIdAndHash冲突
433 // new webpack.optimize.OccurrenceOrderPlugin(),
434 // new webpack.NamedModulesPlugin(),
435 new webpack.optimize.DedupePlugin(), // 去除重复模块 有些JS库有自己的依赖树,并且这些库可能有交叉的依赖,DedupePlugin可以找出他们并删除重复的依赖。
436 new webpack.optimize.UglifyJsPlugin({ // 压缩JS
437 output: {
438 comments: false // remove all comments
439 },
440 compress: {
441 warnings: false
442 }
443 }),
444 // 稳定模块id(当app.js大面积修改时,可能导致vendor里的模块id变化,影响缓存)
445 new WebpackStableModuleIdAndHash()
446 ])
447 }
448 // 开发环境的特殊构建
449 else {
450 config.plugins = config.plugins.concat([
451 // 参考 https://github.com/gaearon/react-hot-boilerplate
452 new webpack.HotModuleReplacementPlugin(),
453 ]);
454 }
455
456 if (shouldBuildSourceMap) {
457 Object.assign(config, {
458 //开启js的sourcemap功能支持
459 // must be 'source-map' or 'inline-source-map'
460 // 详解sourcemap配置: https://segmentfault.com/a/1190000004280859
461 devtool: isProductionENV
462 ? 'source-map' // 生产环境因为有压缩代码,所以必须是source-map保证完整的源码
463 : 'cheap-module-source-map' // 开发环境使用这个cheap-module-source-map就行了,编译速度比source-map更快
464 });
465 }
466
467 console.log('webpack配置:');
468 console.log(JSON.stringify(config, null, 4));
469 return config;
470
471
472}
473
474module.exports = webpackConfigMaker;
\No newline at end of file