通用、封装、简化 webpack 配置

现在,基本上前端的项目打包都会用上 webpack,因为 webpack 提供了无与伦比强大的功能和生态。但在创建一个项目的时候,总是免不了要配置 webpack,很是麻烦。

简化 webpack 配置的一种方式是使用社区封装好的库,比如 roadhogroadhog 封装了 webpack 的一些基础配置,然后暴露一些额外配置的接口,并附加本地数据模拟功能(mock),详情可以参考 roadhog 主页

另一种方式是自己封装 webpack,这样做自己能够更好的掌控项目。

1. 要封装哪些功能

一般搭建一个项目至少需要两种功能:本地开发调试、构建产品代码。

其他的诸如测试、部署到服务器、代码检查、格式优化等功能则不在这篇文章讲解范围,如果有意了解,可以查看我的其他文章。

2. 基础配置

2.1 目录结构(示例,配合后面的代码讲解)


package.json
dev.js # 本地开发脚本
build.js # 产品构建脚本
analyze.js # 模块大小分析(可选) # 单页面结构
src/ # 源代码目录
- index.js # js 入口文件
- index.html # html 入口文件
- ... # 其他文件 # 多页面结构
src/ # 源代码目录
- home/ # home 页面工作空间
- index.js # home 页面 js 入口文件
- index.html # home 页面 html 入口文件
- ... # home 页面其他文件 - explore/ # explore 页面工作空间
- index.js # explore 页面 js 入口文件
- index.html # explore 页面 html 入口文件
- ... # explore 页面其他文件 - about/ # about 目录
- company # about/company 页面工作空间
- index.js # about/company 页面 js 入口文件
- index.html # about/company 页面 html 入口文件
- ... # about/company 页面其他文件 - platform # about/platform 页面工作空间
- index.js # about/platform 页面 js 入口文件
- index.html # about/platform 页面 html 入口文件
- ... # about/platform 页面其他文件 - ... # 更多页面

2.2 基础 npm 包


# package.json "devDependencies": {
"@babel/core": "^7.1.2", # babel core
"@babel/plugin-syntax-dynamic-import": "^7.0.0", # import() 函数支持
"@babel/plugin-transform-react-jsx": "^7.0.0", # react jsx 支持
"@babel/preset-env": "^7.1.0", # es6+ 转 es5
"@babel/preset-flow": "^7.0.0", # flow 支持
"@babel/preset-react": "^7.0.0", # react 支持
"autoprefixer": "^9.1.5", # css 自动添加厂家前缀 -webkit-, -moz-
"babel-loader": "^8.0.4", # webpack 加载 js 的 loader
"babel-plugin-component": "^1.1.1", # 如果使用 element ui,需要用到这个
"babel-plugin-flow-runtime": "^0.17.0", # flow-runtime 支持
"babel-plugin-import": "^1.9.1", # 如果使用 ant-design,需要用到这个
"browser-sync": "^2.24.7", # 浏览器实例组件,用于本地开发调试
"css-loader": "^1.0.0", # webpack 加载 css 的 loader
"chalk": "^2.4.1", # 让命令行的信息有颜色
"file-loader": "^2.0.0", # webpack 加载静态文件的 loader
"flow-runtime": "^0.17.0", # flow-runtime 包
"html-loader": "^0.5.5", # webpack 加载 html 的 loader
"html-webpack-include-assets-plugin": "^1.0.5", # 给 html 文件添加额外静态文件链接的插件
"html-webpack-plugin": "^3.2.0", # 更方便操作 html 文件的插件
"less": "^3.8.1", # less 转 css
"less-loader": "^4.1.0", # webpack 加载 less 的 loader
"mini-css-extract-plugin": "^0.4.3", # 提取 css 单独打包
"minimist": "^1.2.0", # process.argv 更便捷处理
"node-sass": "^4.9.3", # scss 转 css
"optimize-css-assets-webpack-plugin": "^5.0.1", # 优化 css 打包,包括压缩
"postcss-loader": "^3.0.0", # 对 css 进行更多操作,比如添加厂家前缀
"sass-loader": "^7.1.0", # webpack 加载 scss 的 loader
"style-loader": "^0.23.0", # webpack 加载 style 的 loader
"uglifyjs-webpack-plugin": "^2.0.1", # 压缩 js 的插件
"url-loader": "^1.1.1", # file-loader 的升级版
"vue-loader": "^15.4.2", # webpack 加载 vue 的 loader
"vue-template-compiler": "^2.5.17", # 配合 vue-loader 使用的
"webpack": "^4.20.2", # webpack 模块
"webpack-bundle-analyzer": "^3.0.2", # 分析当前打包各个模块的大小,决定哪些需要单独打包
"webpack-dev-middleware": "^3.4.0", # webpack-dev-server 中间件
"webpack-hot-middleware": "^2.24.2" # 热更新中间件
}

2.3 基本命令


# package.json "scripts": {
"dev": "node dev.js",
"build": "node build.js",
"analyze": "node analyze.js",
}

npm run dev # 开发
npm run build # 构建
npm run analyze # 模块分析

如果需要支持多入口构建,在命令后面添加参数:


npm run dev -- home # 开发 home 页面
npm run analyze -- explore # 模块分析 explore 页面 # 构建多个页面
npm run build -- home explore about/* about/all --env test/prod
  • home, explore 确定构建的页面;about/*, about/allabout 目录下所有的页面;all, * 整个项目所有的页面
  • 有时候可能还会针对不同的服务器环境(比如测试机、正式机)做出不同的构建,可以在后面加参数
  • -- 用来分割 npm 本身的参数与脚本参数,参考 npm - run-script 了解详情

2.4 dev.js 配置

开发一般用需要用到下面的组件:


const minimist = require('minimist');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const devMiddleWare = require('webpack-dev-middleware');
const hotMiddleWare = require('webpack-hot-middleware');
const browserSync = require('browser-sync');
const VueLoaderPlugin = require('vue-loader/lib/plugin'); const { HotModuleReplacementPlugin } = webpack; const argv = minimist(process.argv.slice(2)); const page = argv._[0]; // 单页面
const entryFile = `${__dirname}/src/index.js`;
// 多页面
const entryFile = `${__dirname}/src/${page}/index.js`; // 编译器对象
const compiler = webpack({
entry: [
'webpack-hot-middleware/client?reload=true', // 热重载需要
entryFile,
],
output: {
path: `${__dirname}/dev/`, // 打包到 dev 目录
filename: 'index.js',
publicPath: '/dev/',
},
plugins: [
new HotModuleReplacementPlugin(), // 热重载插件
new HtmlWebpackPlugin({ // 处理 html
// 单页面
template: `${__dirname}/src/index.html`,
// 多页面
template: `${__dirname}/src/${page}/index.html`,
}),
new VueLoaderPlugin(), // vue-loader 所需
],
module: {
rules: [
{ // js 文件加载器
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/plugin-transform-react-jsx',
'@babel/plugin-syntax-dynamic-import',
],
},
test: /\.(js|jsx)$/,
},
{ // css 文件加载器
loader: 'style-loader!css-loader',
test: /\.css$/,
},
{ // less 文件加载器
loader: 'style-loader!css-loader!less-loader',
test: /\.less$/,
},
{ // scss 文件加载器
loader: 'style-loader!css-loader!sass-loader',
test: /\.(scss|sass)$/,
},
{ // 静态文件加载器
loader: 'url-loader',
test: /\.(gif|jpg|png|woff|woff2|svg|eot|ttf|ico)$/,
options: {
limit: 1,
},
},
{ // html 文件加载器
loader: 'html-loader',
test: /\.html$/,
options: {
attrs: ['img:src', 'link:href'],
interpolate: 'require',
},
},
{ // vue 文件加载器
loader: 'vue-loader',
test: /\.vue$/,
},
],
},
resolve: {
alias: {}, // js 配置别名
modules: [`${__dirname}/src`, 'node_modules'], // 模块寻址基路径
extensions: ['.js', '.jsx', '.vue', '.json'], // 模块寻址扩展名
},
devtool: 'eval-source-map', // sourcemap
mode: 'development', // 指定 webpack 为开发模式
}); // browser-sync 配置
const browserSyncConfig = {
server: {
baseDir: `${__dirname}/`, // 静态服务器基路径,可以访问项目所有文件
},
startPath: '/dev/index.html', // 开启服务器窗口时的默认地址
}; // 添加中间件
browserSyncConfig.middleware = [
devMiddleWare(compiler, {
stats: 'errors-only',
publicPath: '/dev/',
}),
hotMiddleWare(compiler),
]; browserSync.init(browserSyncConfig); // 初始化浏览器实例,开始调试开发

2.5 build.js 配置

构建过程中,一般会有这些过程:

  1. 提取样式文件,单独打包、压缩、添加浏览器厂家前缀
  2. js 在产品模式下进行打包,并生成 sourcemap 文件
  3. html-webpack-plugin 自动把打包好的样式文件与脚本文件引用到 html 文件中,并压缩
  4. 对所有资源进行 hash 化处理(可选)

const minimist = require('minimist');
const webpack = require('webpack');
const chalk = require('chalk');
const autoprefixer = require('autoprefixer');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin'); const {yellow, red} = chalk; const argv = minimist(process.argv.slice(2)); const pages = argv._; // ['home', 'explore', 'about/*', 'about/all']
const allPages = getAllPages(pages); // 根据 page 中的 `*, all` 等关键字,获取所有真正的 pages // 单页面,只有一个入口,所以只有一个配置文件
const config = { ... }; // 多页面,多个入口,所有有多个配置文件
const configs = allPages.map(page => ({
// 单页面
entry: `${__dirname}/src/index.js`, // js 入口文件
// 多页面
entry: `${__dirname}/src/${page}/index.js`, // js 入口文件
output: {
path: `${__dirname}/dist/`, // 输出路径
filename: '[chunkhash].js', // 输出文件名,这里完全取 hash 值来命名
hashDigestLength: 32, // hash 值长度
publicPath: '/dist/',
},
plugins: [
new MiniCssExtractPlugin({ // 提取所有的样式文件,单独打包
filename: '[chunkhash].css', // 输出文件名,这里完全取 hash 值来命名
}),
new HtmlWebpackPlugin({
// 单页面
template: `${__dirname}/src/index.html`, // html 入口文件
// 多页面
template: `${__dirname}/src/${page}/index.html`,// html 入口文件
minify: { // 指定如果压缩 html 文件
removeComments: !0,
collapseWhitespace: !0,
collapseBooleanAttributes: !0,
removeEmptyAttributes: !0,
removeScriptTypeAttributes: !0,
removeStyleLinkTypeAttributes: !0,
minifyJS: !0,
minifyCSS: !0,
},
}),
new VueLoaderPlugin(), // vue-loader 所需
new OptimizeCssAssetsPlugin({ // 压缩 css
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }],
},
}), // webpack 打包的 js 文件是默认压缩的,所以这里不需要再额外添加 uglifyjs-webpack-plugin
],
module: {
rules: [
{ // js 文件加载器,与 dev 一致
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/plugin-transform-react-jsx',
'@babel/plugin-syntax-dynamic-import',
],
},
test: /\.(js|jsx)$/,
},
{ // css 文件加载器,添加了浏览器厂家前缀
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
autoprefixer({
browsers: [
'> 1%',
'last 2 versions',
'Android >= 3.2',
'Firefox >= 20',
'iOS 7',
],
}),
],
},
},
],
test: /\.css$/,
},
{ // less 文件加载器,添加了浏览器厂家前缀
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
autoprefixer({
browsers: [
'> 1%',
'last 2 versions',
'Android >= 3.2',
'Firefox >= 20',
'iOS 7',
],
}),
],
},
},
'less-loader',
],
test: /\.less$/,
},
{ // scss 文件加载器,添加了浏览器厂家前缀
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
autoprefixer({
browsers: [
'> 1%',
'last 2 versions',
'Android >= 3.2',
'Firefox >= 20',
'iOS 7',
],
}),
],
},
},
'sass-loader',
],
test: /\.(scss|sass)$/,
},
{ // 静态文件加载器,与 dev 一致
loader: 'url-loader',
test: /\.(gif|jpg|png|woff|woff2|svg|eot|ttf|ico)$/,
options: {
limit: 1,
},
},
{ // html 文件加载器,与 dev 一致
loader: 'html-loader',
test: /\.html$/,
options: {
attrs: ['img:src', 'link:href'],
interpolate: 'require',
},
},
{ // vue 文件加载器,与 dev 一致
loader: 'vue-loader',
test: /\.vue$/,
},
],
},
resolve: {
alias: {}, // js 配置别名
modules: [`${__dirname}/src`, 'node_modules'], // 模块寻址基路径
extensions: ['.js', '.jsx', '.vue', '.json'], // 模块寻址扩展名
},
devtool: 'source-map', // sourcemap
mode: 'production', // 指定 webpack 为产品模式
})); // 执行一次 webpack 构建
const run = (config, cb) => {
webpack(config, (err, stats) => {
if (err) {
console.error(red(err.stack || err));
if (err.details) {
console.error(red(err.details));
}
process.exit(1);
} const info = stats.toJson(); if (stats.hasErrors()) {
info.errors.forEach(error => {
console.error(red(error));
});
process.exit(1);
} if (stats.hasWarnings()) {
info.warnings.forEach(warning => {
console.warn(yellow(warning));
});
} // 如果是多页面,需要把 index.html => `${page}.html`
// 因为每个页面导出的 html 文件都是 index.html 如果不重新命名,会被覆盖掉 if(cb) cb();
});
}; // 单页面
run(config); // 多页面
let index = 0;
// go on
const goon = () => {
run(configs[index], () => {
index += 1; if (index < configs.length) goon();
});
}; goon();

2.6 analyze.js 配置


const minimist = require('minimist');
const chalk = require('chalk');
const webpack = require('webpack');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const VueLoaderPlugin = require('vue-loader/lib/plugin'); const {yellow, red} = chalk; const argv = minimist(process.argv.slice(2)); const page = argv._[0]; // 单页面
const entryFile = `${__dirname}/src/index.js`;
// 多页面
const entryFile = `${__dirname}/src/${page}/index.js`; const config = {
entry: entryFile,
output: {
path: `${__dirname}/analyze/`, // 打包到 analyze 目录
filename: 'index.js',
},
plugins: [
new VueLoaderPlugin(), // vue-loader 所需
new BundleAnalyzerPlugin(), // 添加插件
],
module: {
rules: [
{ // js 文件加载器
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/plugin-transform-react-jsx',
'@babel/plugin-syntax-dynamic-import',
],
},
test: /\.(js|jsx)$/,
},
{ // css 文件加载器
loader: 'style-loader!css-loader',
test: /\.css$/,
},
{ // less 文件加载器
loader: 'style-loader!css-loader!less-loader',
test: /\.less$/,
},
{ // scss 文件加载器
loader: 'style-loader!css-loader!sass-loader',
test: /\.(scss|sass)$/,
},
{ // 静态文件加载器
loader: 'url-loader',
test: /\.(gif|jpg|png|woff|woff2|svg|eot|ttf|ico)$/,
options: {
limit: 1,
},
},
{ // html 文件加载器
loader: 'html-loader',
test: /\.html$/,
options: {
attrs: ['img:src', 'link:href'],
interpolate: 'require',
},
},
{ // vue 文件加载器
loader: 'vue-loader',
test: /\.vue$/,
},
],
},
resolve: {
alias: {}, // js 配置别名
modules: [`${__dirname}/src`, 'node_modules'], // 模块寻址基路径
extensions: ['.js', '.jsx', '.vue', '.json'], // 模块寻址扩展名
},
mode: 'production', // 指定 webpack 为产品模式
}; webpack(config, (err, stats) => {
if (err) {
console.error(red(err.stack || err));
if (err.details) {
console.error(red(err.details));
}
process.exit(1);
} const info = stats.toJson(); if (stats.hasErrors()) {
info.errors.forEach(error => {
console.error(red(error));
});
process.exit(1);
} if (stats.hasWarnings()) {
info.warnings.forEach(warning => {
console.warn(yellow(warning));
});
}
});

2.7 扩展配置

你可以根据需要扩展配置,比如添加插件、加载器等,比如:

3. 封装

上面的代码可以封装成一个全局命令,比如 lila,运行上面的命令就可以更简洁:


lila dev home # 开发 home 页面
lila analyze explore # 模块分析 explore 页面 # 构建多个页面
lila build home explore about/* about/all --env test/prod

后续

更多博客,查看 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

通用、封装、简化 webpack 配置的更多相关文章

  1. vue-cli中webpack配置详解

    vue-cli是构建vue单页应用的脚手架,命令行输入vue init <template-name> <project-name>从而自动生成的项目模板,比较常用的模板有we ...

  2. Spring Boot从入门到实战:整合通用Mapper简化单表操作

    数据库访问是web应用必不可少的部分.现今最常用的数据库ORM框架有Hibernate与Mybatis,Hibernate貌似在传统IT企业用的较多,而Mybatis则在互联网企业应用较多.通用Map ...

  3. vue-cli3.x中的webpack配置,优化及多页面应用开发

    官方文档 vue-cli3以下版本中,关于webpack的一些配置都在config目录文件中,可是vue-cli3以上版本中,没有了config目录,那该怎么配置webpack呢? 3.x初始化项目后 ...

  4. webpack配置这一篇就够

    最近看了一篇好文,根据这个文章重新梳理了一遍webpack打包过程,以前的一些问题也都清楚了,在这里分享一下,同时自己也做了一些小的调整 原文链接:http://www.jianshu.com/p/4 ...

  5. 前端工程化(二)---webpack配置

    导航 前端工程化(一)---工程基础目录搭建 前端工程化(二)---webpack配置 前端工程化(三)---Vue的开发模式 前端工程化(四)---helloWord 继续上一遍的配置,本节主要记录 ...

  6. webpack 配置 Vue 多页应用 —— 从入门到放弃

    webpack 配置 Vue 多页应用 -- 从入门到放弃 一直以来,前端享有无需配置,一个浏览器足矣的优势,直到一大堆构建工具的出现,其中 webpack 就是其中最复杂的一个,因此出现了一个新兴职 ...

  7. webpack配置之webpack.config.js文件配置

    webpack配置之webpack.config.js文件配置 webpack.config.js webpack resolve  1.总是手动的输入webpack的输入输出文件路径,是一件非常繁琐 ...

  8. 如何扩展 Create React App 的 Webpack 配置

    如何扩展 Create React App 的 Webpack 配置  原文地址https://zhaozhiming.github.io/blog/2018/01/08/create-react-a ...

  9. angular6 增加webpack配置 亲测可用

    核心 Angular Cli 6 禁用了webpack的自定义配置,官方似乎并未提供自定义配置webpack的方法. 在此之前,可以使用ng eject把默认的webpack提取到代码中,进行自定义. ...

随机推荐

  1. XStream JavaBean对象转换成XML!

    代码实例: 1.javaBean类: package com.hsinfo.web.Demo.XStream; public class City { private String name; pub ...

  2. [BZOJ2164]采矿【模拟+树链剖分+线段树】

    Online Judge:Bzoj2164 Label:模拟,树链剖分,线段树 题目描述 浩浩荡荡的cg大军发现了一座矿产资源极其丰富的城市,他们打算在这座城市实施新的采矿战略.这个城市可以看成一棵有 ...

  3. 第一章 使用开发者模式快速入门 Odoo 12

    本文为最好用的免费ERP系统Odoo 12开发手册系列文章第一篇. Odoo提供了一个快速应用开发框架,非常适合创建商业应用.这类应用通常用于保留业务记录,增删改查操作.Odoo 不仅简化了这类应用的 ...

  4. LUOGU P1903 [国家集训队]数颜色 / 维护队列

    传送门 解题思路 带修莫队,第一次写,其实和普通莫队差不多,就是多了个时间轴,块分n^(2/3)最优,时间复杂度O(n^(5/3)). #include<iostream> #includ ...

  5. 计算机程序是怎么通过cpu,内存,硬盘运行起来的?

    虽然以前知道计算机里有CPU,内存,硬盘,显卡这么些东西,我还真不知道这些东西是怎么协作起来完成一段程序的,能写出程序却不懂程序,也不会向别人解释他们的关系,所以特意总结了一下,写的比较浅显,和我一样 ...

  6. NSIS使用WinVer.nsh头文件判断操作系统版本

    NSIS使用WinVer.nsh头文件判断操作系统版本,首先请下载最新的WinVer.nsh: http://nsis.sourceforge.net/Include/WinVer.nsh(下载后置于 ...

  7. int 13h,磁盘中断

    直接磁盘服务(Direct Disk Service——INT 13H)  00H —磁盘系统复位 01H —读取磁盘系统状态 02H —读扇区 03H —写扇区 04H —检验扇区 05H —格式化 ...

  8. Java中gson的使用

    转https://www.cnblogs.com/qinxu/p/9504412.html

  9. HDFS写数据的过程

  10. Java数据结构和算法(八)--红黑树与2-3树

    红黑树规则: 1.每个节点要么是红色,要么是黑色 2.根节点都是黑色节点 3.每个叶节点是黑色节点 3.每个红色节点的两个子节点都是黑色节点,反之,不做要求,换句话说就是不能有连续两个红色节点 4.从 ...