一.安装步骤
1.新建项目webpack-demo
2.安装webpack
npm install webpack webpack-cli -g
3.项目初始化
npm init -y //-y默认所有的配置
安装依赖环境
npm i webpack webpack-cli -D
//-D webpack安装在devDependencies环境中
二.部署webpack
1.package.json里配置我们的scripts
{
"name": "webpack_demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode production"
//我们在这里配置,就可以使用npm run build 打包我们的项目 像使用vue-cli脚手架那样
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^4.39.3",
"webpack-cli": "^3.3.8"
}
}
三.配置流程
1.Html在webpack中的配置
新建webpack.config.js文件 新建index.html文件
必须都在根目录下面 webpack默认找根目录下的配置
配置我们的入口entry,在vue-cli里相当于跟目录下的main.js,我们的出口output。我们可以把webpack理解为一个工厂,进入相当于把各种各样的原料放进我们的工厂了,然后工厂进行一系列的打包操作把打包好的东西,向外输出,然后就可以去出售了(上线)。
html-webpack-plugin
插件来将 HTML 引用路径和我们的构建结果关联起来。 npm run buid 之后 dist 目录就会多个 index.html 并引入了 main.js.
new HtmlWebpackPlugin({
title: 'webpack-demo',//标题
filename: 'index.html', // 配置输出文件名和路径
template: './index.html', // 配置要被编译的html文件
inject: true,//script标签位于html文件的body底部 true/body/head/false 默认true
favicon: './src/assets/img/logo.png',//指定页面图标 然后在生成的html中就有一个link标签:<link rel='shortcut icon' href='example.ico'>
// 压缩 => production 模式使用
//生产为true 其他的时候为false
minify: {
caseSensitive:false,//是否大小写敏感 默认flase
removeAttributeQuotes: true, //去掉属性引用 删除双引号 默认false
collapseWhitespace: true, //是否去除空格 折叠 html 为一行 默认false
removeComments:true//去注释 默认false
},
hash: true,//是否生成hash添加在引入文件地址的末尾,这个可以避免缓存带来的麻烦。默认为false。
cache: true,//默认是true的,表示内容变化的时候生成一个新的文件。
showErrors: true,//是否将错误信息写在页面里,默认true,出现错误信息则会包裹在一个pre标签内添加到页面上。
chunks:'' //引入的模块,这里指定的是entry中设置多个js时,在这里指定引入的js,如果不设置则默认全部引入。
chunksSortMode: '' //这个选项决定了 script 标签的引用顺序。默认有四个选项,'none' | 'auto' | 'dependency' | 'manual' | {Function}
- none: 无序
- auto: 默认值, 按插件内置的排序方式
- dependency: 根据不同文件的依赖关系排序(一般用于生产)
- manual: chunks按引入的顺序排序, 即属性chunks的顺序
- {Function}: 指定具体的排序规则
})
2.css在webpack中的配置
我们希望使用 webpack 来进行构建 css 文件,为此,需要在配置中引入 loader 来解析和处理 CSS 文件:
npm install style-loader css-loader -D
在src的assets里面的css文件中新增color.css添加样式,在index.js中引入这个css import './assets/css/color.css'
在webpack.config.js中配置loader
module: {
/**
* test: 匹配特定条件。一般是提供一个正则表达式或正则表达式的数组
* include: 匹配特定条件。一般是提供一个字符串或者字符串数组
* exclude: 排除特定条件
* and: 必须匹配数组中的所有条件
* or: 匹配数组中任何一个条件,
* nor: 必须排除这个条件
*/
rules: [
{
test: /\.css$/,
include: [path.resolve(__dirname, 'src')],
use: ['style-loader', 'css-loader']
}
]
}
经由上述两个 loader 的处理后,CSS 代码会转变为 JS, 如果需要单独把 CSS 文件分离出来,我们需要使用 mini-css-extract-plugin 插件
抽取 css 到独立文件, 自动添加前缀
npm i mini-css-extract-plugin postcss-loader autoprefixer -D
我们在写 css 时不免要考虑到浏览器兼容问题,如 transform 属性,需要添加浏览器前缀以适配其他浏览器。故使用到 postcss-loader 这个 loader, 下面则是相关的配置
注意: 如果浏览器前缀添加不成功,记得要在autoprefixer后面加("last 100 versions")
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const devMode = process.env.NODE_ENV !== 'production';
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
include: [path.resolve(__dirname, 'src')],
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',//为了防止css里面有引用背景图片 图片打包之后在dist/images路径错误问题
hmr: devMode, // 仅dev环境启用HMR功能
}
},
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')("last 100 versions")]
}
}
]
}
]
},
plugins: [
//...
new MiniCssExtractPlugin({
// 这里的配置和webpackOptions.output中的配置相似
// 即可以通过在名字前加路径,来决定打包后的文件存在的路径
filename: devMode ? 'css/[name].css' : 'css/[name].[hash].css',
chunkFilename: devMode ? 'css/[id].css' : 'css/[id].[hash].css',
})
]
}
3.图片资源在webpack中的配置
npm install file-loader url-loader -D
file-loader: 可以用于处理很多类型的文件,它的主要作用是直接输出文件,把构建后的文件路径返回。
url-loader: 如果图片较多,会发很多 http 请求,会降低页面性能。url-loader 会将引入的图片编码,生成 dataURl。相当于把图片数据翻译成一串字符。再把这串字符打包到文件中,最终只需要引入这个文件就能访问图片了。当然,如果图片较大,编码会消耗性能。因此 url-loader 提供了一个 limit 参数,小于 limit 字节的文件会被转为 DataURl,大于 limit 的还会使用 file-loader 进行 copy。
- url-loader 可以看作是增强版的 file-loader。
- url-loader 把图片编码成 base64 格式写进页面,从而减少服务器请求。
配置如下:
module.exports = {
module: {
rules: [
// ...
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
outputPath: 'images/', //输出到images文件夹
limit: 10000, //是把小于6000B的文件打成Base64的格式,写入JS
name: "[name]-[hash:5].min.[ext]",
}
}
]
}
]
}
//...
}
4.js在webpack中的配置
现在随着es6的普及,越来越多的代码使用es6了,但是很多浏览器并不支持es6,比如async/awiat,const。因此需要我们引用babe来把我们es6的代码编译为es5。在跟目录下新建.babelrc,简单配置下
{"presets": ["env"]}
安装所需依赖:npm i babel-loader babel-core babel-preset-env -D
{
test: /\.js$/, //es6 => es5
exclude: /node_modules/,
use: 'babel-loader'
}
报错:Error: Cannot find module '@babel/core' babel-loader@8 requires Babel 7.x (the package '@babel/core'). If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7' 因为babel-loader的版本问题
先卸载 npm uninstall babel-loader 重装一个@7的版本 npm i babel-loader@7 -D
四:打包前清理源目录文件 clean-webpack-plugin
npm install clean-webpack-plugin -D
配置如下:
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin(['dist'])
]
}
报错:TypeError: CleanWebpackPlugin is not a constructor 修改引用方式:const {CleanWebpackPlugin} = require('clean-webpack-plugin');
又报错:Error: clean-webpack-plugin only accepts an options object 修改调用方式,不传入目录:new CleanWebpackPlugin()
五:提取公用代码
假如你 a.js 和 b.js 都 import 了 c.js 文件,这段代码就冗杂了。为什么要提取公共代码,简单来说,就是减少代码冗余,提高加载速度。
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
// 抽离自己写的公共代码
chunks: 'initial',
name: 'common', // 打包后的文件名,任意命名
minChunks: 2, //最小引用2次
minSize: 0 // 只要超出0字节就生成一个新包
},
styles: {
name: 'styles', // 抽离公用样式
test: /\.css$/,
chunks: 'all',
minChunks: 2,
enforce: true
},
vendor: {
// 抽离第三方插件
test: /node_modules/, // 指定是node_modules下的第三方包
chunks: 'initial',
name: 'vendor', // 打包后的文件名,任意命名
// 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
priority: 10
}
}
}
}
}
六:hash
hash 是干嘛用的? 我们每次打包出来的结果可能都是同一个文件,那我上线的时候是不是要替换掉上线的 js,那我怎么知道哪是最新的呢,我们一般会清一下缓存。而 hash 就是为了解决这个问题而存在的
module.exports = {
//...
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:8].js'
},
//...
plugins: [
new MiniCssExtractPlugin({
filename: devMode ? 'css/[name].css' : 'css/[name].[hash].css',
chunkFilename: devMode ? 'css/[id].css' : 'css/[id].[hash].css',
})
]
}
七:减少 resolve 的解析,配置别名
如果我们可以精简 resolve 配置,让 webpack 在查询模块路径时尽可能快速地定位到需要的模块,不做额外的查询工作,那么 webpack 的构建速度也会快一些
module.exports = {
resolve: {
/**
* alias: 别名的配置
*
* extensions: 自动解析确定的扩展,
* 比如 import 'xxx/theme.css' 可以在extensions 中添加 '.css', 引入方式则为 import 'xxx/theme'
* @default ['.wasm', '.mjs', '.js', '.json']
*
* modules 告诉 webpack 解析模块时应该搜索的目录
* 如果你想要添加一个目录到模块搜索目录,此目录优先于 node_modules/ 搜索
* 这样配置在某种程度上可以简化模块的查找,提升构建速度 @default node_modules 优先
*/
extensions: ['.vue', '.js', '.json','.css'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@':
path.resolve(__dirname, 'src'),
'@components':
path.resolve(__dirname, 'src/components/'),
'@assets':
path.resolve(__dirname, 'src/assets'),
},
modules: [path.resolve(__dirname, 'src'), 'node_modules']
}
}
八:webpack-dev-serve
安装:npm install webpack-dev-server -D package.json 中 scripts 中添加:"dev": "webpack-dev-server --mode development"
devServer: {
host: '0.0.0.0',//主机名
port: 8080,//端口
open: true,//自动打开浏览器
compress: true,//服务器压缩
hot:true,//热更新
inline:true,//页面自动刷新
//跨域问题
proxy:{
'/api': {
target: 'https://testbi.promni.cn/v2/api',
secure: false,
changeOrigin: true,
pathRewrite: {'^/api' : ''}
}
},
},
九:模块热替换(hot module replacement)
模块热替换(HMR - Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
保留在完全重新加载页面时丢失的应用程序状态。 只更新变更内容,以节省宝贵的开发时间。 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。
上面我们 npm start 后修改一次文件,页面就会刷新一次。这样就存在很大问题了,比如我们使用 redux, vuex 等插件,页面一刷新那么存放在 redux, vuex 中的东西就会丢失,非常不利于我们的开发。
import webpack from 'webpack';
plugins: [
new webpack.HotModuleReplacementPlugin()
//...
]
配置后还不行,因为 webpack 还不知道你要更新哪里, 修改 src/index.js 文件, 添加
if (module.hot) {
module.hot.accept()
}
但是但是有个问题是,你修改 css/less 等样式文件并未发生改变, what ? HMR 修改样式表 需要借助于 style-loader, 而我们之前用的是 MiniCssExtractPlugin.loader, 这也好办,修改其中一个 rules 就可以了,我们可以试试改
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
// MiniCssExtractPlugin.loader,
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')] // 添加css中的浏览器前缀
}
},
'less-loader'
]
}
]
}
}
我们可以发现,dev 下配置的 loader 为 style-loader , 而生产环境下则是需要 MiniCssExtractPlugin.loader 这就涉及到了不同环境之间的配置。可以通过 process.env.NODE_ENV 获取当前是开发环境或者是生产环境,然后配置不同的 loader,这里就不做展开了
十:完整webpack代码
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
//打包入口
entry: {
main: './src/index.js',
page1: "./src/assets/js/page1.js",//测试提取公用代码
page2: "./src/assets/js/page2.js",//测试提取公用代码
},
//打包出口
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:8].js'
},
module: {
/**
* test: 匹配特定条件。一般是提供一个正则表达式或正则表达式的数组
* include: 匹配特定条件。一般是提供一个字符串或者字符串数组
* exclude: 排除特定条件
* and: 必须匹配数组中的所有条件
* or: 匹配数组中任何一个条件,
* nor: 必须排除这个条件
*/
rules: [
{
test: /\.css$/,
include: [path.resolve(__dirname, 'src')],
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',//为了防止css里面有引用背景图片 图片打包之后在dist/images路径错误问题
hmr: devMode, // 仅dev环境启用HMR功能
}
},
// 'style-loader', 模块热替换时候用的
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')("last 100 versions")]//添加浏览器前缀
}
}
]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
outputPath: 'images/', //输出到images文件夹
limit: 6000, //是把小于6000B的文件打成Base64的格式,写入JS
name: "[name]-[hash:5].min.[ext]",
}
}
]
},
{
test: /\.js$/, //es6 => es5
exclude: /node_modules/,
use: 'babel-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'webpack-demo',//标题
filename: 'index.html', // 配置输出文件名和路径
template: './index.html', // 配置要被编译的html文件
favicon: './src/assets/img/logo.png',//指定页面图标 然后在生成的html中就有一个link标签:<link rel='shortcut icon' href='example.ico'>
// 压缩 => production 模式使用
minify: {
removeAttributeQuotes: true, //去掉属性引用 删除双引号
collapseWhitespace: true, //是否去除空格 折叠 html 为一行
removeComments: true//去注释
},
hash: true,//是否生成hash添加在引入文件地址的末尾,这个可以避免缓存带来的麻烦。默认为true。
}),
new MiniCssExtractPlugin({
// 这里的配置和webpackOptions.output中的配置相似
// 即可以通过在名字前加路径,来决定打包后的文件存在的路径
filename: devMode ? 'css/[name].css' : 'css/[name].[hash].css',
chunkFilename: devMode ? 'css/[id].css' : 'css/[id].[hash].css',
}),
//清理源目录文件
new CleanWebpackPlugin(),
//模块热替换
new webpack.HotModuleReplacementPlugin()
],
optimization: {
//压缩文件 mode:production 生产环境配置
minimizer: [
new OptimizeCssAssetsWebpackPlugin(),// 压缩css
new UglifyJsPlugin()//压缩js
],
//提取公用代码
splitChunks: {
cacheGroups: {
commons: {
// 抽离自己写的公共代码
chunks: 'initial',
name: 'common', // 打包后的文件名,任意命名
minChunks: 2, //最小引用2次
minSize: 0 // 只要超出0字节就生成一个新包
},
styles: {
name: 'styles', // 抽离公用样式
test: /\.css$/,
chunks: 'all',
minChunks: 2,
enforce: true
},
vendor: {
// 抽离第三方插件
test: /node_modules/, // 指定是node_modules下的第三方包
chunks: 'initial',
name: 'vendor', // 打包后的文件名,任意命名
// 设置优先级,防止和自定义的公共代码提取时被覆盖,不进行打包
priority: 10
}
}
}
},
// 减少 resolve 的解析,配置别名
resolve: {
extensions: ['.vue', '.js', '.json', '.css'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@':
path.resolve(__dirname, 'src'),
'@components':
path.resolve(__dirname, 'src/components/'),
'@assets':
path.resolve(__dirname, 'src/assets'),
},
},
//webpack-dev-server mode:development 开发环境配置
devServer: {
host: '0.0.0.0',//主机名
port: 8080,//端口
open: true,//自动打开浏览器
compress: true,//服务器压缩
hot: true,//热更新
inline: true,//页面自动刷新
//跨域问题
proxy: {
'/api': {
target: 'https://testbi.promni.cn/v2/api',
secure: false,
changeOrigin: true,
pathRewrite: {'^/api': ''}
}
},
},
};