webpack是目前比较主流的一款打包,压缩工具。下面我们来详细介绍webpack4.0搭建一个项目的配置详解。目的是为了帮助各位前端攻城狮们在配置webpack时达到最优,最简化的自定义前端框架。(内容比较多且繁杂,不强求各位需要耐心看完,但看完对日后构建前端架构绝对是有一些帮助的)
- 首先介绍下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语法转换组件提供勾选,无法编译成功则表示转换组件缺少)