webpack4.0配置使用详解

1,048 阅读9分钟
原文链接: zhuanlan.zhihu.com

webpack是目前比较主流的一款打包,压缩工具。下面我们来详细介绍webpack4.0搭建一个项目的配置详解。目的是为了帮助各位前端攻城狮们在配置webpack时达到最优,最简化的自定义前端框架。(内容比较多且繁杂,不强求各位需要耐心看完,但看完对日后构建前端架构绝对是有一些帮助的)

  1. 首先介绍下webpack4.X搭建项目的主要配置文件
  • package.json (webpack项目的前端库管理,项目详细介绍的配置)

配置内容:

①项目作者,时间,版本,功能介绍

②项目开发中使用的前端库

③运行环境要求,限制

④项目运行脚本命令配置

.........

下面来一段package.json文件,具体内容根据注释详解,需要耐心读取

{
  "name": "wshifu-user",
  "version": "2.0.0",
  "description": "万师傅用户中心",
  "main": "index.js",
  "repository": "http://git.wanshifu.com/wanshifu/wshifu-user.git",
  "author": "xiao·zhang",
  "license": "MIT",
  "scripts": {
    "clean": "rimraf assets/*",
    "dev:dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.config.js --color --progress --hot",
    "dev:test": "cross-env NODE_ENV=testing webpack-dev-server --config build/webpack.dev.config.js --color --progress --hot",
    "build:dev": "cross-env NODE_ENV=development webpack --config build/webpack.build.config.js",
    "build:prod": "cross-env NODE_ENV=production webpack --config build/webpack.build.config",
    "build:test": "cross-env NODE_ENV=testing webpack --config build/webpack.build.config"
  },
  "devDependencies": {  /* 项目编译运行所使用的组件库 */
    "babel-core": "^6.26.3",                              //JavaScript编译核心babel-core(必须)
    "babel-loader": "^7.1.4",                               //JavaScript编译babel-loader(必须)
    "babel-plugin-transform-decorators-legacy": "^1.3.4", //JavaScript 装饰器语法编译器(JavaScript 使用@装饰)
    "babel-plugin-transform-react-jsx": "^6.23.0",  //react jsx语法转换器
    "babel-plugin-transform-runtime": "^6.23.0",  //JavaScript 运行环境转换器
    "babel-plugin-react-html-attrs": "^2.0.0",   //react dom属性识别语法转换器
    "babel-preset-latest": "^6.24.1",           //ES6语法编译器(包含ES2015,ES2016,ES2017)
    "babel-preset-react": "^6.24.1",            //react语法编译器
    "babel-preset-stage-0": "^6.24.1",          //ES6,ES7语法转换ES5编译器(ES7的提案,且包含了stage-2,stage-1所有功能)
    "babel-polyfill": "^6.26.0",                //提供ES2015+的运行环境(必须)
    "babel-runtime": "^6.26.0",                //提供将ES6转变成ES5运行环境,并不污染全局完成代码填充
    "file-loader": "^1.1.11",                  
    "json-loader": "^0.5.7",
    "url-loader": "^0.6.2",
    "style-loader": "^0.18.2",              //样式loader(必须)
    "css-loader": "^0.28.11",               //css-loader (必须)
    "autoprefixer": "^8.5.1",               //css3自动补全功能
    "postcss-cssnext": "^3.1.0",           //利用cssnext 额外增加的一些 css 规范,
    "postcss-loader": "^2.1.5",
    "less": "^2.7.3",                     //以下都是使用less相关语法加载库
    "less-loader": "^4.1.0",
    "less-vars-to-js": "^1.2.1",
    "node-sass": "^4.5.3",                //以下都是使用sass,scss相关语法加载库
    "sass-loader": "^6.0.6",
    "mini-css-extract-plugin": "^0.4.0",  //css压缩组件库
    "react-loadable": "^4.0.5",
    "dotenv": "^4.0.0",                  //webpack环境变量加载库(具体使用见webpack文件)
    "dotenv-webpack": "^1.5.4",          //webpack环境变量加载库(具体使用见webpack文件)
    "cross-env": "^5.1.6",               //配置多命令运行库(见上方scripts选项中运行命令配置中的使用)
    "concurrently": "^3.5.0",
    "prettier": "^1.6.0",
    "ncp": "^2.0.0",
    "rimraf": "^2.6.2",                 //文件夹删除管理库(见上方scripts选项中运行命令配置中的使用)
    "webpack": "^4.8.3",
    "webpack-cli": "^2.1.4",            //webpack脚手架库(可选)
    "webpack-merge": "^4.1.2",          //webpack配置合并库
    "webpack-dev-server": "^3.1.4",     //webpack-dev-server
    "html-webpack-plugin": "^3.2.0",    //webpack 首页组装组件库
    "copy-webpack-plugin": "^4.0.1",    //webpack 文件复制组件库
    "clean-webpack-plugin": "^0.1.19",  //webpack 文件清除,移除组件库
    "compression-webpack-plugin": "^1.1.11", //webpack gzip压缩组件库
    "webpack-manifest-plugin": "^1.3.1",   //webpack 缓存配置组件库
    "chunk-manifest-webpack-plugin": "^1.1.2",//webpack 缓存配置相关组件库
    "webpack-parallel-uglify-plugin": "^1.1.0",//webpack js压缩组件库
    "optimize-css-assets-webpack-plugin": "^4.0.1",//webpack css压缩相关组件库
    "extract-text-webpack-plugin": "^4.0.0-alpha.0",//webpack css压缩相关组件库
    "open-browser-webpack-plugin": "0.0.5" //webpack 项目启动自动打开浏览器组件库
  },
  "dependencies": {  /* 项目开发中所使用的组件库 */
    "axios": "^0.16.2",
    "echarts": "^3.7.1",
    "history": "^4.7.2",
    "lodash": "^4.17.4",
    "prop-types": "^15.5.10",
    "qs": "^6.5.1",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-redux": "^5.0.6",
    "react-router-dom": "^4.2.2",
    "react-router-redux": "~5.0.0-alpha.6",
    "react-tap-event-plugin": "^2.0.1",
    "react-transition-group": "^2.2.0",
    "recompose": "^0.25.0",
    "redux": "^3.7.2",
    "redux-form": "^7.1.0",
    "redux-thunk": "^2.2.0",
    "redux-logger": "^3.0.6",
  },
  "engines": { /* 运行环境限制说明 */
    "node": ">8.9.0"
  }
}
  • .babelrc (JavaScript语法编译器babel的配置,基本上包含了JavaScript所有最新语法编译,以下配置中的各选项可根据项目使用JavaScript语法的情况进行增删,对应的package.json中的组件库也需增删。具体内容根据注释详解,需要耐心读取)
{
    "presets": [  /* 预置语法解析器组件 */
        "react",  //react语法转换编译器
        "latest", //包含了ES2015,ES2016,ES2017故此使用该库时无需在使用ES2015或ES2016或ES2017
        "stage-0" //ES7语法转换ES5编译器
    ],
    "plugins": [ /* 补充语法解析器组件(预置编译无法通过情况下使用) */
        "transform-decorators-legacy", //JavaScript @ 装饰器语法转换器
        "transform-react-jsx",         //jsx 语法转换器
        [
            "transform-runtime",
            {
                "helpers": false,
                "polyfill": false,
                "regenerator": true,
                "moduleName": "babel-runtime"
            }
        ],
        "react-html-attrs"        //react dom属性转换器
    ]
}
  • postcss.config.js(css语法规范编译器配置 用途:用于css语法编译解析)
module.exports = {
    plugins: {  /* 补充css语法解析器组件(预置编译无法通过情况下使用) */
        'postcss-cssnext': {},
    }
};
  • webpack.config.js (webpack编译配置详解·重点。 该配置一般由全局配置,环境变量配置,webpack原始读取配置 三部分配置文件构成,详细见下解析)

1· config.js (全局配置。 用途:便捷编辑、修改,是否启用webpack某项功能,该配置最终需要导入<3·webpack原始读取配置>中通过js装配,变换生成webpack读取的json类型数据)

/*
 * @Author: xiao·Zhang 
 * @Date: 2018-06-06 09:22:18 
 * @Last Modified by: xiao·Zhang
 * @Last Modified time: 2018-06-06 09:35:43
 * @file: 开发环境和编译环境自定制功能配置文件 
 */

const path = require('path')

module.exports = {
  dev: {
    devUrl : 'http://localhost:8080',
    assetsRoot: 'assets',   //打包文件路径
    assetsPublicPath: '/', //webAPP根路径
    proxyTable: {   //代理列表 支持多个代理
      '/api': {
          target: 'http://dev-user-api.wanshifu.com/',
          changeOrigin: true,
          withCredentials: true,
          secure: false,
          pathRewrite: { '^/api': '' }
      },
    },
    autoOpenBrowser: true,
    host: 'localhost', 
    port: 8080, 
    devtool: 'cheap-module-eval-source-map',
  },
  build: {
    index: path.resolve(__dirname, '../assets/index.html'),
    assetsRoot: path.resolve(__dirname, '../assets'),
    // assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    productionSourceMap: false,
    devtool: false,
    productionGzip: false,  //是否压缩启用
    productionGzipExtensions: ['js', 'css'],  //压缩启用-压缩的内容
    deleteOriginalAssets : true  //压缩启用-删除压缩的源文件
  }
}

2· env (环境变量配置。 用途:项目代码根据所运行的环境对应加载不同的变量,代码中类似此类参数应该配置在环境变量中,由webpack编译时载入变量,代码书写时直接获取环境变量,无需再考虑运行环境时对应的参数变化【如开发,测试,生成等环境】

例如生产【测试】环境运行项目时,前端连接的后台接口API路径变化:

.env.production (生产环境后台API)

USER_URL=http://user.wanshifu.com/
USER_API=http://user-api.wanshifu.com/
WWW_URL=http://www.wanshifu.com/
WORKER_URL=http://worker.wanshifu.com/

.env.testing(测试环境后台API)

USER_URL=http://test-user.wanshifu.com/
USER_API=http://test-user-api.wanshifu.com/
WWW_URL=http://test-www.wanshifu.com/
WORKER_URL=http://test-worker.wanshifu.com/

以上文件由webpack的原始读取配置中的 dotenv组件读取载入,在下方webpack原始读取配置中的公用配置中plugins配置使用(详情见下方 3-③)。

3· webpack.config.js (webpack原始读取配置·重重点 用途:供webpack直接读取的配置文件,该文件通过JavaScript代码组合、变换的方式最终返回一组供webpack组件读取使用的json数据【一般分为三部分:开发、编译、公用部分的配置】, 最终该文件在上方<package.json >文件中的scripts配置运行命令选项时提供webpack,webpack-dev-server运行使用的配置,下面配置主要看完编译环境和公用配置即可,开发环境是编译环境的简化版,更易看懂)

①· 开发环境webpack.dev.config.js

/*
 * @Author: xiao·Zhang 
 * @Date: 2018-06-06 09:24:13 
 * @Last Modified by: xiao·Zhang
 * @Last Modified time: 2018-06-06 09:33:03
 * @file: 开发环境webpack参数配置  
 */

const merge = require('webpack-merge');
const path = require('path');
const webpack = require('webpack');
const commonConfig = require('./webpack.common.config.js');
const OpenBrowserPlugin = require('open-browser-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const config = require('./config');

const devConfig = {
	mode: 'development',
	devtool: config.dev.devtool,//开启相应SourceMap模式,加了导致热更新减慢
	entry: { //项目入口js, 可以配置多个,入口配置多少,对应就会有多少output的内容,输出自动遍历输入。
		app: [path.join(__dirname, '../app/index.js') ] 
	},
	output: { //项目输出
		path: path.resolve(__dirname, config.dev.assetsRoot), //打包文件输出目录
		publicPath: config.dev.assetsPublicPath, //项目访问的根目录,类似与定义一个base路径
		filename: 'js/[name].js?[hash]',
		chunkFilename: 'js/[name].js?[hash]'
	},
	module: {
		rules: [{
			test: /\.css$/,
			use: ['style-loader', 'css-loader', 'postcss-loader']
		}]
	},
	devServer: { //webpack-dev-server启动配置
		contentBase: path.join(__dirname, './dists'),//webpack-dev-server输出目录
                publicPath: config.dev.assetsPublicPath,//webpack-dev-server启动时访问的根目录
		port: config.dev.port,                  //webpack-dev-server启动的端口(本地开发启动)
		host: config.dev.host,
		proxy: config.dev.proxyTable,
		compress: true,
		historyApiFallback: true,         //开启H5history模式(该模式需要后台配合,所以本地开发是也需要开启)
        },
	plugins: [  //webpack使用的插件集合
		new webpack.DefinePlugin({// 配置运行环境
			'process.env': {
				NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
			},
		}),
		new CopyWebpackPlugin([{ //文件拷贝,从from文件夹到to文件夹,可配置多个拷贝
			from: path.resolve(__dirname, '../app/assets/javascripts'),
			to: path.posix.join('javascripts'),
			ignore: ['.*']
		}, {
			from: path.resolve(__dirname, '../app/assets/fonts'),
			to: path.posix.join('fonts'),
			ignore: ['.*']
		}, {
			from: path.resolve(__dirname, '../ie-upgrade.html'),
			ignore: ['.*']
		}]),
	]
};
if(config.dev.autoOpenBrowser){
	devConfig.plugins.push(new OpenBrowserPlugin({url: `http://localhost:${config.dev.port + config.dev.assetsPublicPath}`}));  //配置自动打开浏览器路径
}
//合并公用配置
module.exports = merge({
	customizeArray(a, b, key) {
		/*entry.app不合并,全替换*/
		if (key === 'entry.app') {
			return b;
		}
		return undefined;
	}
})(commonConfig, devConfig);

②· 编译环境webpack.build.config.js

/*
 * @Author: xiao·Zhang 
 * @Date: 2018-06-06 09:23:29 
 * @Last Modified by: xiao·Zhang
 * @Last Modified time: 2018-06-06 09:47:45
 * @file: 编译环境webpack参数配置 
 */

const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const commonConfig = require('./webpack.common.config.js');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
const config = require('./config');

const publicConfig = {
	mode: 'production',
	devtool: config.build.devtool,
	entry: {
		app: [path.join(__dirname, '../app/index.js')]
	},
	output: {
		path: config.build.assetsRoot,
		publicPath: config.build.assetsPublicPath,
		filename: 'js/[name].[hash].js',
		chunkFilename: 'js/[name].[hash].js'
	},
	module: {
		rules: [
			{
				test: /\.css$/,
				use: [
					MiniCssExtractPlugin.loader, //压缩css,压缩配置使用OptimizeCSSAssetsPlugin
					"css-loader",
					'postcss-loader',
				]
			}
		]
	},
	optimization: {
		minimizer: [
			// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
			new ParallelUglifyPlugin({
				// 传递给 UglifyJS 的参数
				uglifyJS: {
					compress: {
						warnings: false,
						drop_debugger: true,
						drop_console: true
					},
					output: {
						comments: false
					},
				},
				sourceMap: config.build.productionSourceMap
			}),
			new OptimizeCSSAssetsPlugin({})  // use OptimizeCSSAssetsPlugin
		]
	},
	plugins: [
		new CleanWebpackPlugin(['../assets/*.*']),
		new webpack.DefinePlugin({
			'process.env': {
				NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'production')
			},
		}),
		new CopyWebpackPlugin([{
			from: path.resolve(__dirname, '../app/assets/javascripts'),
			to: path.posix.join('javascripts'),
			ignore: ['.*']
		}, {
			from: path.resolve(__dirname, '../app/assets/fonts'),
			to: path.posix.join('fonts'),
			ignore: ['.*']
		}, {
			from: path.resolve(__dirname, '../ie-upgrade.html'),
			ignore: ['.*']
		}]),
		new webpack.optimize.ModuleConcatenationPlugin(),
		new MiniCssExtractPlugin({
			filename: 'css/[name].[hash].css',
			chunkFilename: 'css/[name].[hash].css'
		}),
	]
};
//启用webpack压缩组件
if (config.build.productionGzip) {
	var CompressionWebpackPlugin = require('compression-webpack-plugin')
	publicConfig.plugins.push(
		new CompressionWebpackPlugin({
		asset: '[path].gz[query]',
		algorithm: 'gzip',
		test: new RegExp(
			'\\.(' + config.build.productionGzipExtensions.join('|') + ')$'
		),
		threshold: 10240,  //最小开启压缩大小
		minRatio: 0.8,
		deleteOriginalAssets: config.build.deleteOriginalAssets //是否删除源文件
		})
	)
}

module.exports = merge({
	customizeArray(a, b, key) {
		/*entry.app不合并,全替换*/
		if (key === 'entry.app') {
			return b;
		}
		return undefined;
	}
})(commonConfig, publicConfig);

③· 公用部分webpack.common.config.js

/*
 * @Author: xiao·Zhang 
 * @Date: 2018-06-06 09:24:35 
 * @Last Modified by: xiao·Zhang
 * @Last Modified time: 2018-06-06 09:56:03
 * @file: 开发和编译环境公用webpack参数配置  
 */

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const Dotenv = require('dotenv-webpack')
const webpack = require('webpack');
// const lessToJs = require('less-vars-to-js');
const fs = require('fs');

/**
 * @param {string} dir
 * @returns 合成绝对路径
 */

const resolve = function (dir) {
    return path.join(__dirname, '..', dir)
}

//打印校对当前启动的环境
console.log('--------NODE_ENV---------', process.env.NODE_ENV)

const commonConfig = {
	entry: {
		app: [
			path.join(__dirname, '../app/index.js')
		],
	 	vendors: ["axios","prop-types","react","react-dom","react-redux","react-router-dom","react-router-redux","redux"] //分离第三方库,可自定义增减
	},
	output: {
		path: path.join(__dirname, '../dist'),
		filename: 'js/[name].js?[chunkhash]',
		chunkFilename: 'js/[name].js?[chunkhash]',
		publicPath: "/"
	},
	module: {
		rules: [{
            test: /\.js$/,
            use: ['babel-loader?cacheDirectory=true'],
            exclude: path.resolve(__dirname, 'node_modules/'),
        }, {
            test: /\.(png|jpg|gif|ico|jpeg|bmp)$/,
            exclude: path.resolve(__dirname, 'node_modules/'),
            use: [{
                loader: 'url-loader',
                options: {
                    limit: 10000,
                    name: 'images/[name].[ext]'
                }
            }]
        }, {
            test: /\.less$/,
            use: [
                'style-loader',
                { loader: 'css-loader', options: { importLoaders: 1 }},
                'postcss-loader',
                {loader:'less-loader', options: { modifyVars: {}}}
            ]
        }, {
        test: /\.(scss|sass)$/,
            use: [
                { loader: 'style-loader', options: { sourceMap: true }},
                { loader: 'css-loader', options: { sourceMap: true}},
                { loader: 'postcss-loader', options: { sourceMap: true }},
                { loader: 'sass-loader', options: { sourceMap: true }}
            ]
        }],
	},
	optimization: {
		splitChunks: {
			chunks: 'initial', // 只对入口文件处理
			cacheGroups: {
				vendor: { // split `node_modules`目录下被打包的代码到 `js/chunks/vendor.js
					test: /node_modules\//,
					name: 'chunks/vendor',
					priority: 10,
					enforce: true,
					reuseExistingChunk: true   // 可设置是否重用该chunk(查看源码没有发现默认值)
				},
				commons: { // split `common`和`components`目录下被打包的代码到`js/chunks/commons.js `
					test: /common\/|components\//,
					name: 'chunks/commons',
					priority: 10,
					enforce: true,
					reuseExistingChunk: true   // 可设置是否重用该chunk(查看源码没有发现默认值)
				}
			}
		},
		runtimeChunk: {
			name: 'manifests/manifest'
		}
	},
	plugins: [
		new HtmlWebpackPlugin({
			filename: 'index.html',
			favicon: 'app/assets/images/favicon.ico',
                        template: resolve('index.html')
                }),
		new webpack.HashedModuleIdsPlugin(),
		new Dotenv({             //装载对应环境变量
			path: path.resolve(`./build/env/.env.${process.env.NODE_ENV}`),
			safe: false,
			systemvars: false
		})
	],
	externals: {
		jquery: 'window.$'
	},
	resolve: {
		alias: {   //文件别名
			'ajax': resolve('app/utils/request'),
                        'validator': resolve('app/utils/validator'),
                        'config': resolve('app/config'),
			'images': resolve('assets/images'),
			'@assets': resolve('app/assets')
                },
		modules: [resolve('app'), 'node_modules'],
		extensions: [".js", ".jsx", ".json"]
	}
};
module.exports = commonConfig;

2· 针对上方配置,补充babel语法编译器知识点。babel编译的作用是将ES6甚至更前卫ES7提案语法编译成浏览能识别的ES5语法代码在浏览器中执行。babel学习官网地址 (官网为英文,这里找的是中文翻译篇)该网站介绍了ES6新增特性,并且提供了ES6代码测试工具 (使用方法左边输入框编写ES6语法,右边输入框对应生成ES5语法,最左边有一系列的babel语法转换组件提供勾选,无法编译成功则表示转换组件缺少)