为什么我们要做三份 Webpack 配置文件


Posted in Javascript onSeptember 18, 2017

在知乎上我们常常会看到有同学发问:BAT 等大型网站的前端工程是如何组织管理的?这的确是一个可以发散的很广的 Q&A,我想如果要我回答这个问题,不如先从 Webpack 配置说起。

时至今日,Webpack 已经成为前端工程必备的基础工具之一,不仅被广泛用于前端工程发布前的打包,还在开发中担当本地前端资源服务器(assets server)、模块热更新(hot module replacement)、API Proxy 等角色,结合 ESLint 等代码检查工具,还可以实现在对源代码的严格校验检查。

正如上文中提到的,前端从开发到部署前都离不开 Webpack 的参与,而 Webpack 的默认配置文件只有一个,即 webpack.config.js,那么问题来了,开发期和部署前应该使用同一份 Webpack 配置吗?答案肯定是否定的,既然 webpack.config.js 是一个 JS 文件,我们当然可以在文件里写 JavaScript 业务逻辑,通过读取环境变量 NODE_ENV 来判断当前是在开发(dev)时还是最终的生产环境(production),然而很多同学习惯把这两者的配置都混写在根目录下的 webpack.config.js,通过很多零散的 if...else 来“临时”决定某一个 plugin 或者某一个 loader 的配置项,随着 loaders 和 plugins 的不断增加,久而久之 webpack.config.js 变得原来越隆长,代码的可读性和可维护性也大大下降。

我想通过本文来介绍一种用 3 个 JS 文件来配置 Webpack 的方法,这里借鉴了很多开源项目的配置,同时也结合了我们自己在开发中碰到的种种问题解决方案。

        本文中提及的配置基于 Webpack 2 或以上,建议使用 3.0 及以上版

开发环境与生产环境的区别

开发环境

 ·NODE_ENV 为 development

 ·启用模块热更新(hot module replacement)

 ·额外的 webpack-dev-server 配置项,API Proxy 配置项

 ·输出 Sourcemap

生产环境

 ·NODE_ENV 为 production

 · 将 React、jQuery 等常用库设置为 external,直接采用 CDN 线上的版本

 · 样式源文件(如 css、less、scss 等)需要通过 ExtractTextPlugin 独立抽取成 css 文件

 · 启用 post-css

 · 启用 optimize-minimize(如 uglify 等)

 ·中大型的商业网站生产环境下,是绝对不能有 console.log() 的,所以要为 babel 配置Remove console transform

这里需要说明的是因为开发环境下启用了 hot module replacement,为了让样式源文件的修改也同样能被热替换,不能使用 ExtractTextPlugin,而转为随 JS Bundle 一起输出。

你需要三份配置文件

1. webpack.base.config.js

在 base 文件里,你需要将开发环境和生产环境中通用的配置集中放在这里:

const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
// 配置常量
// 源代码的根目录(本地物理文件路径)
const SRC_PATH = path.resolve('./src');
// 打包后的资源根目录(本地物理文件路径)
const ASSETS_BUILD_PATH = path.resolve('./build');
// 资源根目录(可以是 CDN 上的绝对路径,或相对路径)
const ASSETS_PUBLIC_PATH = '/assets/';
module.exports = {
 context: SRC_PATH, // 设置源代码的默认根路径
 resolve: {
  extensions: ['.js', '.jsx'] // 同时支持 js 和 jsx
 },
 entry: {
  // 注意 entry 中的路径都是相对于 SRC_PATH 的路径
  vendor: './vendor',
  a: ['./entry-a'],
  b: ['./entry-b'],
  c: ['./entry-c']
 },
 output: {
  path: ASSETS_BUILD_PATH,
  publicPath: ASSETS_PUBLIC_PATH,
  filename: './[name].js'
 },
 module: {
  rules: [
   {
    enforce: 'pre', // ESLint 优先级高于其他 JS 相关的 loader
    test: /\.jsx?$/,
    exclude: /node_modules/,
    loader: 'eslint-loader'
   },
   {
    test: /\.jsx?$/,
    exclude: /node_modules/,
    // 建议把 babel 的运行时配置放在 .babelrc 里,从而与 eslint-loader 等共享配置
    loader: 'babel-loader'
   },
   {
    test: /\.(png|jpg|gif)$/,
    use:
    [
     {
      loader: 'url-loader',
      options:
      {
       limit: 8192,
       name: 'images/[name].[ext]'
      }
     }
    ]
   },
   {
    test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
    use:
    [
     {
      loader: 'url-loader',
      options:
      {
       limit: 8192,
       mimetype: 'application/font-woff',
       name: 'fonts/[name].[ext]'
      }
     }
    ]
   },
   {
    test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
    use:
    [
     {
      loader: 'file-loader',
      options:
      {
       limit: 8192,
       mimetype: 'application/font-woff',
       name: 'fonts/[name].[ext]'
      }
     }
    ]
   }
  ]
 },
 plugins: [
  // 每次打包前,先清空原来目录中的内容
  new CleanWebpackPlugin([ASSETS_BUILD_PATH], { verbose: false }),
  // 启用 CommonChunkPlugin
  new webpack.optimize.CommonsChunkPlugin({
   names: 'vendor',
   minChunks: Infinity
  })
 ]
};

2. webpack.dev.config.js

这是用于开发环境的 Webpack 配置,继承自 base:

const webpack = require('webpack');

// 读取同一目录下的 base config
const config = require('./webpack.base.config');

// 添加 webpack-dev-server 相关的配置项
config.devServer = {
 contentBase: './',
 publicPath: '/assets/'
};
// 有关 Webpack 的 API 本地代理,另请参考 https://webpack.github.io/docs/webpack-dev-server.html#proxy 
config.module.rules.push(
 {
  test: /\.less$/,
  use: [
   'style-loader',
   'css-loader',
   'less-loader'
  ],
  exclude: /node_modules/
 }
);
// 真实场景中,React、jQuery 等优先走全站的 CDN,所以要放在 externals 中
config.externals = {
 react: 'React',
 'react-dom': 'ReactDOM'
};

// 添加 Sourcemap 支持
config.plugins.push(
 new webpack.SourceMapDevToolPlugin({
  filename: '[file].map',
  exclude: ['vendor.js'] // vendor 通常不需要 sourcemap
 })
);
// Hot module replacement
Object.keys(config.entry).forEach((key) => {
 // 这里有一个私有的约定,如果 entry 是一个数组,则证明它需要被 hot module replace
 if (Array.isArray(config.entry[key])) {
  config.entry[key].unshift(
   'webpack-dev-server/client?http://0.0.0.0:8080',
   'webpack/hot/only-dev-server'
  );
 }
});
config.plugins.push(
 new webpack.HotModuleReplacementPlugin()
);
module.exports = config;

3. webpack.config.js

这是用于生产环境的 webpack 配置,同样继承自 base:

const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// 读取同一目录下的 base config
const config = require('./webpack.base.config');
config.module.rules.push(
 {
  test: /\.less$/,
  use: ExtractTextPlugin.extract(
   {
    use: [
     'css-loader',
     'less-loader'
    ],
    fallback: 'style-loader'
   }
  ),
  exclude: /node_modules/
 }
);
config.plugins.push(
 // 官方文档推荐使用下面的插件确保 NODE_ENV
 new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
 }),
 // 启动 minify
 new webpack.LoaderOptionsPlugin({ minimize: true }),
 // 抽取 CSS 文件
 new ExtractTextPlugin({
  filename: '[name].css',
  allChunks: true,
  ignoreOrder: true
 })
);
module.exports = config;

现在在你的工程文件夹里应该已经有三个 Webpack 配置文件,它们分别是:

 · webpack.base.config.js
 
· webpack.dev.config.js

 · webpack.config.js

最后,你还需要在 package.json 里添加相应的配置:

{
 ...
 "scripts": {
  "build": "webpack --optimize-minimize",
  "dev": "webpack-dev-server --config webpack.dev.config.js",
  "start": "npm run dev" // 或添加你自己的 start 逻辑
 },
 ...
}

和很多项目一样,在开发环境下的时候,你需要使用 npm run dev 来启动,而在生产环境中,则用 npm run build 来发布。
题外话,在真实场景中,我们不会直接使用 webpack-dev-server,而采用 express + webpack/webpack-dev-middleware,配置方法与上面所述的完全相同。

关于专栏

如果你喜欢这篇文章,就请关注我的专栏《前端零栈》,在这里我们一起聊一聊前端技术和前端工程。

关于作者

Henry,10 岁开始学习计算机编程,高二暑假获得江苏省青少年信息奥林匹克一等奖。2000 年开始自学 JavaScript 及网页制作,2006 年起正式开始从事前端开发工作,从此一干就是 10 多年。加入阿里巴巴前,曾在 SAP 中国研究院担任智慧交通大数据产品经理。

Github:MagicCube (Henry Li)

 小结

前端从开发到部署前都离不开 Webpack 的参与,本文结合了我们自己在开发中碰到的种种问题解决方案,同时借鉴了很多开源项目的配置来介绍一种用 3 个 JS 文件来配置 Webpack 的方法。关于本文如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
javascript 选择文件夹对话框(web)
Jul 07 Javascript
jquery调用wcf并展示出数据的方法
Jul 07 Javascript
ASP.NET jQuery 实例15 通过控件CustomValidator验证CheckBoxList
Feb 03 Javascript
优化Jquery,提升网页加载速度
Nov 14 Javascript
在JavaScript中判断整型的N种方法示例介绍
Jun 18 Javascript
详解AngularJS中的表达式使用
Jun 16 Javascript
jQuery通过deferred对象管理ajax异步
May 20 Javascript
js图片切换具体实现代码
Oct 13 Javascript
从零学习node.js之利用express搭建简易论坛(七)
Feb 25 Javascript
angularjs实现搜索的关键字在正文中高亮出来
Jun 13 Javascript
基于node下的http小爬虫的示例代码
Jan 11 Javascript
element-ui 时间选择器限制范围的实现(随动)
Jan 09 Javascript
分析javascript中9 个常见错误阻碍你进步
Sep 18 #Javascript
十个免费的web前端开发工具详细整理
Sep 18 #Javascript
Redux 和 Mobx的选择问题:让你不再困惑!
Sep 18 #Javascript
HTML5开发Kinect体感游戏的实例应用
Sep 18 #Javascript
聊聊那些使用前端Javascript实现的机器学习类库
Sep 18 #Javascript
Web技术实现移动监测的介绍
Sep 18 #Javascript
探索webpack模块及webpack3新特性
Sep 18 #Javascript
You might like
php如何获取文件的扩展名
2015/10/28 PHP
PHP实现的超长文本分页显示功能示例
2018/06/04 PHP
jQuery插件开发的两种方法及$.fn.extend的详解
2014/01/16 Javascript
jQuery+css实现的时钟效果(兼容各浏览器)
2016/01/27 Javascript
jQuery日历插件datepicker用法详解
2016/03/03 Javascript
微信小程序 购物车简单实例
2016/10/24 Javascript
基于vue实现多引擎搜索及关键字提示
2017/03/16 Javascript
vue组件表单数据回显验证及提交的实例代码
2018/08/30 Javascript
JavaScript实现简单随机点名器
2019/11/21 Javascript
使用Vue-cli 中为单独页面设置背景图片铺满全屏
2020/07/17 Javascript
原生js实现购物车功能
2020/09/23 Javascript
vue中封装axios并实现api接口的统一管理
2020/12/25 Vue.js
使用Python的Twisted框架编写简单的网络客户端
2015/04/16 Python
python获取本地计算机名字的方法
2015/04/29 Python
Python字符串处理实例详解
2017/05/18 Python
python 时间信息“2018-02-04 18:23:35“ 解析成字典形式的结果代码详解
2018/04/19 Python
Pycharm在创建py文件时,自动添加文件头注释的实例
2018/05/07 Python
详解Django rest_framework实现RESTful API
2018/05/24 Python
python日志logging模块使用方法分析
2019/05/23 Python
Python完成毫秒级抢淘宝大单功能
2019/06/06 Python
python+opencv实现车牌定位功能(实例代码)
2019/12/24 Python
python  ceiling divide 除法向上取整(或小数向上取整)的实例
2019/12/27 Python
python的flask框架难学吗
2020/07/31 Python
详解Python中import机制
2020/09/11 Python
Python中三维坐标空间绘制的实现
2020/09/22 Python
Crucial英睿达法国官网:内存条及SSD固态硬盘升级
2018/07/13 全球购物
获奖的大学生创业计划书
2014/01/05 职场文书
财务主管自我鉴定
2014/01/17 职场文书
中式餐厅创业计划书范文
2014/01/23 职场文书
竞争性谈判邀请书
2014/02/06 职场文书
美术指导求职信
2014/03/17 职场文书
勤奋学习演讲稿
2014/05/10 职场文书
Requests什么的通通爬不了的Python超强反爬虫方案!
2021/05/20 Python
Python实现机器学习算法的分类
2021/06/03 Python
python获取对象信息的实例详解
2021/07/07 Python
MySQL中几种插入和批量语句实例详解
2021/09/14 MySQL