前言:文件依赖关系错综复杂,静态资源请求效率低,模块化支持不友好,浏览器对高级JS兼容程度低?那就是时候了解webpack了
一、简单了解webpack
(1)概念
webpack 是一个JavaScript 应用程序的静态模块打包构建器。在处理应用程序时, webpack 会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。webpack提供了友好的模块化支持,以及代码压缩混淆、高级js兼容、性能优化。
(2)核心
1.入口(entry):指定webpack打包编译从哪个文件开始下手
入口起点(entry point)指示 webpack 使用哪个模块,作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
webpack.config.js:
module.exports = {
entry: {
main: './src' //打包入口,来指定一个入口起点(或多个入口起点,默认值为 ./src)
},
entry: './src', //这个是上面的简写方式,是等价的
entry: {
home: "./home.js",
about: "./about.js",
contact: "./contact.js"
},//对象法指定多个入口,如果你想要多个依赖一起注入到一个模块,向 entry 属性传入「文件路径(file path)数组」。
entry: () => new Promise((resolve) => resolve(['./demo', './demo2'])),//动态入口,当结合 output.library 选项时:如果传入数组,则只导出最后一项
};
2.出口(output):指定webpack打包编译后的路径及文件名
output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。
webpack.config.js:
const path = require('path');
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),//打包文件夹名,默认值为 ./dist
filename: '[name].js'//入口文件名
}
};
3.loader(加载器):webpack识别不了的语言通过加载器来翻译
loader 用于转换某些类型的模块,webpack 自身只理解 JavaScript,loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块。loader 能够 import
导入任何类型的模块(如 .css
),是 webpack 特有的功能,其他打包工具有可能不支持。
webpack.config.js:
const path = require('path');
const config = {
module: {
rules: [//在 webpack 配置中定义 loader 时,要定义在 module.rules 中,里面包含两个"必须属性":test 和 use
{
test: /\.txt$/, //test 定义需要使用相应 loader 进行转换的某类文件
use: 'raw-loader' //use 定义使用哪个 loader 进行转换
}
]
}
};
module.exports = config;
4.插件(plugins):webpack完成不了的功能通过插件来完成
插件接口功能极其强大,可以用来处理各种各样的任务。通过require()
使用插件,然后把它添加到 plugins
数组中。多数插件可以通过选项(option)自定义。
webpack.config.js:
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
const config = {
plugins: [
//在一个配置文件中因为不同目的而多次使用同一个插件,需要通过使用 new 操作符来创建它的一个实例
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
module.exports = config;
5.模式(mode):通过模式确定是开发环境还是生产环境以便加载不同配置
module.exports = {
mode: 'production'//通过选择 development(开发) 或 production(生产),启用相应模式下的 webpack 内置的优化
};
6.配置
webpack 需要传入一个配置对象, 根据对象定义的属性进行解析,因此很少有 webpack 配置看起来很完全相同。 webpack 的配置文件,是导出一个对象的 JavaScript 文件。可以通过两种方式(终端、Node.js)使用 webpack。
webpack 配置是标准的 Node.js CommonJS 模块,你可以:
- 通过
require(...)
导入其他文件 - 通过
require(...)
使用 npm 的工具函数 - 使用 JavaScript 控制流表达式,例如
?:
操作符 - 对常用值使用常量或变量
- 编写并执行函数来生成部分配置
应避免以下做法:
- 在使用 webpack 命令行接口(CLI)(应该编写自己的命令行接口(CLI),或使用
--env
)时,访问命令行接口(CLI)参数 - 导出不确定的值(调用 webpack 两次应该产生同样的输出文件)
- 编写很长的配置,应该将配置拆分为多个文件
二、动手搭建一个
(1)前提必备
webpack 配置是标准的 Node.js CommonJS 模块,在安装webpack之前,请确保安装了 Node.js 的最新版本,使用旧版本可能遇到各种问题(可能缺少 webpack 功能或者缺少相关 package 包)。
(2)准备工作
1.安装webpack
对于大多数项目,建议本地安装。这可以在引入破坏式变更(breaking change)的依赖时,更容易分别升级项目。不推荐全局安装 webpack。这会将你项目中的 webpack 锁定到指定版本,并且在使用不同的 webpack 版本的项目中,可能会导致构建失败。
①本地安装
npm install --save-dev webpack //安装最新版本
npm install --save-dev webpack@<version> //安装特定版本
②全局安装
npm install --global webpack
2. 安装 CLI
如果你使用 webpack 4+ 版本,你还需要安装 CLI,此工具用于在命令行中运行 webpack。
npm install --save-dev webpack-cli //webpack-cli用于在命令行中运行 webpack
(3)上手操作
在已有的项目中:
npm init -y //初始化webpack 这里会自动生成一个package.json
npm i -D webpack webpack-cli //安装webpack及其脚手架
一个项目包两个项目demo1和demo2(适用于两个项目功能需求很类似有公用的地方有不同的地方):
{
"name": "webpackstudy",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"demo1-start": "webpack-dev-server --progress --color",
"demo2-start": "webpack-dev-server --progress --color",
"demo1-mock": "webpack-dev-server --progress --color",
"demo2-mock": "webpack-dev-server --progress --color",
"demo1-te": "webpack --progress --color",
"demo2-te": "webpack --progress --color"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.19.0",
"vue": "^2.6.10",
"vue-pull-to": "^0.1.8",
"vue-router": "^3.1.2",
"vuex": "^3.1.1"
},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"autoprefixer": "^9.6.1",
"babel-loader": "^8.0.6",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.2.0",
"file-loader": "^4.2.0",
"html-webpack-plugin": "^3.2.0",
"html-withimg-loader": "^0.1.16",
"jsonc": "^2.0.0",
"less": "^3.10.2",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.8.0",
"mocker-api": "^1.8.1",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"style-loader": "^1.0.0",
"terser-webpack-plugin": "^1.4.1",
"uglifyjs-webpack-plugin": "^2.2.0",
"url-loader": "^2.1.0",
"vue-loader": "^15.7.1",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.39.2",
"webpack-cli": "^3.3.7",
"webpack-dev-server": "^3.8.0"
}
}
const path = require('path');
const webpack = require('webpack');
// script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题; 自动创建html入口文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 用terser-webpack-plugin替换掉uglifyjs-webpack-plugin解决uglifyjs不支持es6语法问题
const TerserJSPlugin = require('terser-webpack-plugin');
// 此模块至少需要Node v6.9.0和webpack v4.0.0 混淆代码
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
// 这个插件将CSS提取到单独的文件中 支持按需加载CSS和SourceMaps 建立在webpack v4上
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 用于优化、压缩CSS资源
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// 打包时将之前打包的目录里的文件先清除干净,再生成新的
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
//mock数据
const apiMocker = require('mocker-api');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
//获取 npm run 后面的命令
const lifecycle = process.env.npm_lifecycle_event;
//npm run 的文件名
const project = lifecycle.split('-')[0];
//npm run 的环境名
const proMode = lifecycle.split('-')[1] === 'te';
const envMode = lifecycle.split('-')[1];
const webpackConfig = {
mode: proMode ? 'production' : 'development',
//打包入口为每个项目的main.js
entry: path.resolve(__dirname, `${project}/main.js`),
output: {
//打包后的文件目录为各自项目名+Dist
path: path.resolve(__dirname, `${project}Dist`),
//打包后源代码映射
// devtool: proMode ?'cheap-module-eval-source-map':'hidden-source-map',
// devtool: "inline-source-map",
//打包后的出口js目录
filename: 'js/[name].[hash].js',
//分块打包的js目录
chunkFilename: proMode ? 'js/[name].[contenthash].bundle.js' : 'js/[name].bundle.js',
},
module: {
rules: [
{
test: /\.vue$/,
exclude: /node_modules/,
use: {
loader: 'vue-loader'
}
},
{
test: /\.(le|c)ss$/i,
use: [
proMode ? MiniCssExtractPlugin.loader :
'vue-style-loader',
'css-loader',
'postcss-loader',
'less-loader',
],
},
{
test: /\.html$/i,
use: [
'html-withimg-loader'
]
},
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
name: 'img/[name].[contenthash].[ext]'
},
},
],
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
//webpack-dev-server 配置npm run 时启动本地服务
devServer: {
contentBase: `./${project}Dist`,
inline: true //实时刷新
},
//优化
optimization: {
// 分块
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
}
}
},
plugins: [
new CleanWebpackPlugin(),
//定义插件—— 在项目中可以读取到
new webpack.DefinePlugin({
'baseUrl':proMode ? 'https:www.baidu.com':JSON.stringify('localhost')
}),
new HtmlWebpackPlugin({
title:'webpack练习',
filename: 'index.html',
template: `${project}/index.html`,
// 对 html 文件进行压缩
minify: {
//是否对大小写敏感,默认false
caseSensitive: false,
//是否简写boolean格式的属性如:disabled="disabled" 简写为disabled 默认false
collapseBooleanAttributes: true,
//是否去除空格,默认false
collapseWhitespace: true,
//是否压缩html里的css(使用clean-css进行的压缩) 默认值false;
minifyCSS: true,
//是否压缩html里的js(使用uglify-js进行的压缩)
minifyJS: true,
//是否移除注释 默认false
removeComments: true,
//Prevents the escaping of the values of attributes
preventAttributesEscaping: true,
//是否移除属性的引号 默认false
removeAttributeQuotes: true,
//从脚本和样式删除的注释 默认false
removeCommentsFromCDATA: true,
//是否删除空属性,默认false
removeEmptyAttributes: false,
// 若开启此项,生成的html中没有 body 和 head,html也未闭合
removeOptionalTags: false,
//删除多余的属性
removeRedundantAttributes: true,
//删除script的类型属性,在h5下面script的type默认值:text/javascript 默认值false
removeScriptTypeAttributes: true,
//删除style的类型属性, type="text/css" 同上
removeStyleLinkTypeAttributes: true,
//使用短的文档类型,默认false
useShortDoctype: false,
}
}),
new VueLoaderPlugin()
],
//目录映射
resolve: {
alias: {
'@assets': path.resolve(__dirname, `${project}/assets`),
'@mixins': path.resolve(__dirname, `${project}/mixins`),
'@tools': path.resolve(__dirname, `${project}/tools`),
'@components': path.resolve(__dirname, `${project}/components`),
}
}
};
if (proMode) {
webpackConfig.optimization.minimizer = [
//混淆语法
new UglifyJsPlugin({
chunkFilter: (chunk) => {
if (chunk.name === 'vendor') {
return false;
}
return true;
},
//去掉控制台日志
uglifyOptions: {
compress: {
drop_console: true
}
}
}),
new OptimizeCssAssetsPlugin({})
];
// webpackConfig.optimization.minimizer = [new TerserJSPlugin({}),
// new OptimizeCssAssetsPlugin({}),];
webpackConfig.plugins.push(
new MiniCssExtractPlugin({
filename: proMode ? '[name].css' : '[name].[hash].css',
chunkFilename: proMode ? '[id].css' : '[id].[hash].css',
})
)
} else {
// 热更新模块
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
webpackConfig.devtool = 'inline-source-map';
if (envMode == 'mock') {
//mock环境,启用mock代理服务
webpackConfig.devServer.before = (app) => {
apiMocker(app, path.resolve(`${project}/mock/api.js`));
};
//非mock匹配项走测试环境
webpackConfig.devServer.proxy = process.baseUrl;
}
}
module.exports = webpackConfig;