首先贴上自己关键插件版本
"webpack": "^5.52.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.1.1"
本文引用依赖较多,请注意配置是否已失效。
一、初识webpack
1、配置文件名称
webpack默认配置文件:webpack.config.js
可以通过webpack --config指定配置文件
module.exports = {
entry: './src/index.js', // 4.0会默认制定入口位置为‘src/index.js’
output: './dist/main.js', // 4.0会默认制定入口位置为‘dist/main.js’
mode: 'production', // 环境
module: {
rules: [ // loader配置
{
test:/\.txt$/, use: 'raw-loader'
}
]
},
plugins:[
new HtmlwebpackPlugin({ // 插件配置
template: './src/index.html'
})
]
}
2、安装nvm
安装 nvm(node.js version management,顾名思义是一个nodejs的版本管理工具。通过它可以安装和切换不同版本的nodejs。下面列出下载、安装及使用方法。)
安装命令:
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
or Wget:
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
安装完之后添加到环境变量。
source ~/.bash_profile
// 推出并重启终端,查看是否安装成功:
nvm --version
// 安装node.js:
nvm i v10.15.3
// 创建项目文件夹,并初始化
mkdir 01project
cd 01project
// 所有询问都是yes
npm init -y
//安装webpack
npm i webpack webpack-cli --save-dev
//查看项目是否安装成功
./node_modules/.bin/webpack -v
webpack 5.52.0
webpack-cli 4.8.0
3、一个简单例子
新建webpack.config.js文件
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js'
},
mode: 'production'
}
新建src/index.js、src/helloworld.js文件
// index.js文件
import { helloworld } from "./helloworld";
document.write(helloworld())
// helloworld.js文件
export function helloworld(){
return 'hello webpack'
}
运行./node_modules/.bin/webpack命令,打包文件
新建dist/index.html文件,并引入打包文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./bundle.js"></script>
</body>
</html>
4、通过npm script运行webpack
为什么package.json可以直接运行node_module/.bin的命令
原理:模块局部安装会在node_module/.bin目录创建软链接
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build":"webpack"
},
新建src/serach.js文件
document.write('search info')
修改webpack.config.js文件 //通过占位符确保文件名称唯一
const path = require('path')
module.exports = {
entry: {
'index': './src/index.js',
'search': './src/search.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
mode: 'production'
}
修改dist/index.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./index.js"></script>
<script src="./search.js"></script>
</body>
</html>
5、核心概念之Loaders
webpack开箱即用只支持JS和JSON两种文件类型,通过Loaders去支持其他文件类型并且把她们转化成有效的模块,并且可以添加到依赖图中。
本身是一个函数,接受源文件作为参数,返回转换的结果。
常用的loaders有哪些
6、核心概念之Loaders
插件用于bundle文件的优化,资源管理和环境变量注入,作用域整个构建过程。
二、常用Loaders
2.1、解析es6、React JSX
2.1.1 使用babel-loader
安装相关依赖
npm install -D babel-loader @babel/core @babel/preset-env webpack
webpack配置
module:{
rules: [
{
test: /\.js$/,
use: 'babel-loader'
}
]
}
为了支持es6需要增加配置
module:{
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
option: {
presets: ["@babel/preset-env"],
plugins: ['@babel/plugin-proposal-object-rest-spread']
}
}
}
]
}
又或者给babel一个配置文件.babelrc,新建.babelrc文件
{
"presets": ["@babel/preset-env"]
}
2.1.2 解析React JSX
安装相关依赖
npm install -D react-dom @babel/preset-react
修改.babelrc文件
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
修改src/search.js文件,编写react组件
import React from 'react'
import ReactDOM from 'react-dom'
class Search extends React.Component{
render() {
return <div>search components</div>
}
}
ReactDOM.render(
<Search/>,
document.getElementById('root')
)
2.2 css解析相关
2.2.1css-loader、style-loader(css-loader解析css、style-loader将样式通过<style>标签插入到head中)
安装依赖
npm i -D css-loader style-loader
修改webpack.config.js文件
module:{
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
}
},
{
test: /\.css$/,
use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
'style-loader',
'css-loader',
]
}
]
}
新建src/css/index.css文件
.search-txt {
font-size: 20px;
color: blue;
}
在src/search.js文件中引用css文件
import React from 'react'
import ReactDOM from 'react-dom'
import './css/index.css'
class Search extends React.Component{
render() {
return <div className="search-txt ">search components</div>
}
}
ReactDOM.render(
<Search/>,
document.getElementById('root')
)
2.2.2 解析less和sass
安装依赖
npm i -D less less-loader
修改src/css/index.css文件为src/css/index.less
修改src/css/search.js内关于index.css的引用名
修改webpack文件
module:{
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
}
},
{
test: /\.css$/,
use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
'style-loader',
'css-loader',
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
]
}
]
}
2.3 资源解析
2.3.1 file-loader用于处理图片
安装依赖npm i -D file-loader
在src/img文件夹下放置一张图片
在src/search.js文件中引入图片
import React from 'react'
import ReactDOM from 'react-dom'
import './css/index.less'
import fileImg from './img/fileImg.png'
class Search extends React.Component{
render() {
return <div className="search-txt ">search components
<img src={fileImg}></img>
</div>
}
}
ReactDOM.render(
<Search/>,
document.getElementById('root')
)
添加相关配置
module:{
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
}
},
{
test: /\.css$/,
use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
'style-loader',
'css-loader',
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
]
},
{
test: /\.(png|jpeg|gif|jpg)$/,
use: [
'file-loader']
}
]
}
2.3.1 file-loader用于处理字体
新建src/font文件夹,放入字体文件
css引入,修改src/css/index.css文件
@font-face{
font-family: 'liuKai';
src: url('../font/liuKai.ttf')
}
.search-txt {
font-size: 20px;
color: blue;
font-family: 'liuKai';
}
修改webpack配置
module:{
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
}
},
{
test: /\.css$/,
use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
'style-loader',
'css-loader',
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
]
},
{
test: /\.(png|jpeg|gif|jpg)$/,
use: [
'file-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader']
}
]
}
2.3.2 url-loader也可以处理图片和字体,可以设置较小资源自动base64
安装依赖
npm i -D url-loader
webpack.config.js中修改关于图片处理的部分
{
test: /\.(png|jpeg|gif|jpg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240 // 限制大小为10k
}
}]
},
2.4 文件监听(watch)
文件监听是在发现源码发生变化时,自动重新构建出新的输出文件
webpack开启监听模式,有两种方式
- 启动webpack命令时,带上--watch参数
- 在配置webpack.config.js中设置watch:true
缺陷:每次需要手动刷新浏览器
watch: true, //默认为false 不开启
watchOptions: { // 只有watch开启时,watchOptions才会有意义
ignored: /node_modules/, // 不坚挺的文件或文件夹,支持正则匹配
aggregateTimeout: 300, //监听到变化后300ms再去执行,默认300ms
poll: 1000 //判断文件是否变化的轮询间隔时间(有变化先混存,到期再更新)
}
2.5 热更新:webpack-dev-server(使用HotModuleReplacementPlugin插件)
wds不刷新浏览器
wds不输出文件,而是放在内存中
安装依赖
npm install webpack-dev-server -D
"scripts": {
"dev": "webpack-dev-server --open"//有更新自动打开浏览器
},
由于热更新主要是用在开发环境中,修改mode: development
由于HotModuleReplacementPlugin是内部插件
const webpack = require('webpack')
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer: { // 专门为webpack-dev-server指定相关配置选项
hot: true,
static: {
directory: path.resolve(__dirname, "dist"),
}
},
运行之后会有一点报错,点开报错文件修改数据就好了。
热更新的原理
- webpack compile:将JS变异成bundle
- HRM Server:将热更新的文件输出给HMR Runtime
- Bundle Server:提供文件在浏览器的访问
- HMR Runtime:会注入到浏览器,更新文件的变化
- bundle.js: 构建输出的文件
2.5 文件指纹(ContentHash,ChunkHash,Hash)
文件指纹如何生成
在webpack
中有三种hash
可以配置
Hash: 和整个项目构建相关,只要项目文件有修改,整个项目构建的hash值就会更改
ChunkHash: 和webpack打包的chunk有关,不同的entry会生成不同的chunkhash的值
ContentHash: 根据文件内容来定义hash,文件内容不变,则contenthash不变
2.5.1 js文件的指纹设置(chunkhash在生产环境使用,无法和热更新HotModuleReplacementPlugin一起使用)
设置output的filename,使用[chunkhash]// filename: '[name].[chunkhash].js'
新建一份生产环境专用的webpack配置文件webpack.prod.js,
设置mode: 'production',去掉热更新相关模块。
设置文件指纹长度filename: '[name]_[chunkhash:8].js'
设置图片、字体的文件指纹
{
test: /\.(png|jpeg|gif|jpg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8][ext]'
}
}]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8][ext]'
}
}]
}
const path = require('path')
module.exports = {
entry: {
'index': './src/index.js',
'search': './src/search.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js'
},
mode: 'production',
module:{
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
}
},
{
test: /\.css$/,
use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
'style-loader',
'css-loader',
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
]
},
{
test: /\.(png|jpeg|gif|jpg)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]'
}
}]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash:8].[ext]'
}
}]
}
]
}
}
添加prod执行命令
"prod":"webpack --config webpack.prod.js"
2.5.2 css文件指纹设置(plugin:MiniCssExtractPlugin)
设置MiniCssExtractPlugin的filename,使用[contenthash]
// filename: '[name].[contenthash].js'
安装依赖
npm i -D mini-css-extract-plugin
引入插件,加入plugin数组中
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
});
]
}
同时还需要添加到依赖中。MiniCssExtractPlugin的功能是将css样式抽离到css文件,而style-loader是将样式以<style>标签的形式引入,二者有所冲突,可以去除style-loader。
{
test: /\.css$/,
use: [ //webapck use执行顺序从右到左、链式调用,所以这里先执行css-loader
MiniCssExtractPlugin.loader,
'css-loader',
]
},
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
]
},
2.5.2 图片、字体文件指纹设置
设置file-loader的filename,使用[hash]
// filename: '[name].[hash].js'
2.6 文件压缩 (prod环境)
2.6.1 js 文件的压缩(内置的uglifyjs-webpack-plugin)
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
plugins: [
new OptimizeCssAssetsWebpackPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require('cssnano')
})
]
}
2.6.2 css 文件的压缩 optimize-css-assets-webpack-plugin
使用optimize-css-assets-webpack-plugin
同时使用cssnano
安装依赖
npm i optimize-css-assets-webpack-plugin -D
npm i -D cssnano
引入依赖
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
plugins: [
new OptimizeCssAssetsWebpackPlugin()
]
}
2.6.3 html 文件的压缩(html-webpack-plugin prod环境)
修改html-webpack-plugin,设置压缩参数(模板文件不能有注释)
安装依赖
npm i -D html-webpack-plugin
引入html
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname,'src/search.html'),// 需要打包的文件路径
filename: 'search.html',
chunks: ['search'], //
inject: true, // 设置为true,js css会自动注入html
minify: {
}
}),
new HtmlWebpackPlugin({
template: path.join(__dirname,'src/index.html'),// 需要打包的文件路径
filename: 'index.html',
chunks: ['index'], //
inject: true, // 设置为true,js css会自动注入html
minify: {//压缩选项
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCss: true,
minifyJs: true,
removeComments: false
}
})
]
}
2.7 自动清理构建目录产物(clean-webpack-plugin)
会自动清除output目录
安装依赖, 引入插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}
2.8 自动补齐css前缀(postcss-loader、autoprefixer prod环境)
安装依赖
npm i -D postcss-loader postcss autoprefixer
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
{
loader: 'postcss-loader',
}
]
},
新建postcss.config.js文件进行配置
module.exports = {
plugins: [
require('autoprefixer')({
overrideBrowserslist: [
'last 2 version',
'>1%',
'ios 7']
})
]
}
2.8 移动端CSS px自动转换成rem
px2rem-loader设置尺寸稿。
lib-flexible保证
安装依赖
npm i -D px2rem-loader
//动态计算根元素单位
npm i lib-flexible -S
lib-flexible会自动在html的head中添加一个meta name="viewport"
的标签,同时会自动设置html的font-size为屏幕宽度除以10,也就是1rem等于html根节点的font-size。
配合2.91的raw-loader使用
引入使用
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
{
loader: 'postcss-loader',
},
{
loader: 'px2rem-loader',
options: {
remUnit: 75, // 1rem = 75px
remPrecision: 8 // 转换时的位数
}
}
]
},
]
}
}
在html内引入lib-flexible代码,后期使用2.8.1介绍raw-loader引入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script>
$( require('raw-loader!babel-loader./meta.html') )
; (function (win, lib) {
var doc = win.document;
var docEl = doc.documentElement;
var metaEl = doc.querySelector('meta[name="viewport"]');
var flexibleEl = doc.querySelector('meta[name="flexible"]');
var dpr = 0;
var scale = 0;
var tid;
var flexible = lib.flexible || (lib.flexible = {});
if (metaEl) {
console.warn('将根据已有的meta标签来设置缩放比例');
var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
} else if (flexibleEl) {
var content = flexibleEl.getAttribute('content');
if (content) {
var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
if (initialDpr) {
dpr = parseFloat(initialDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
if (maximumDpr) {
dpr = parseFloat(maximumDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
}
}
if (!dpr && !scale) {
var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
if (isIPhone) {
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他设备下,仍旧使用1倍的方案
dpr = 1;
}
scale = 1 / dpr;
}
docEl.setAttribute('data-dpr', dpr);
if (!metaEl) {
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
var wrap = doc.createElement('div');
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
width = 540 * dpr;
}
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
flexible.rem = win.rem = rem;
}
win.addEventListener('resize', function () {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener('pageshow', function (e) {
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
if (doc.readyState === 'complete') {
doc.body.style.fontSize = 12 * dpr + 'px';
} else {
doc.addEventListener('DOMContentLoaded', function (e) {
doc.body.style.fontSize = 12 * dpr + 'px';
}, false);
}
refreshRem();
flexible.dpr = win.dpr = dpr;
flexible.refreshRem = refreshRem;
flexible.rem2px = function (d) {
var val = parseFloat(d) * this.rem;
if (typeof d === 'string' && d.match(/rem$/)) {
val += 'px';
}
return val;
}
flexible.px2rem = function (d) {
var val = parseFloat(d) / this.rem;
if (typeof d === 'string' && d.match(/px$/)) {
val += 'rem';
}
return val;
}
})(window, window['lib'] || (window['lib'] = {}));
</script>
</body>
</html>
2.8.1 静态资源内联 raw-loader
资源内联的意义
代码层面:
- 页面框架的初始化脚本
- 上报相关打点
- css内联避免页面闪动
请求层面:减少HTTP网络请求数
- 小图片或者字体内联(url-loader)
HTML和JS内联 raw-loader(读取文件返回一个string,插入合适的位置)
css内联
- 借助style-loader
- html-inline-css-webpack-plugin
安装依赖
npm i raw-loader@0.5.1 -D
新建src/meta.html文件,存储相关配置,作为静态资源等待引入
<meta name="keywords" content="CSDN博客,CSDN学院,CSDN论坛,CSDN直播">
<meta name="description"
content="CSDN是全球知名中文IT技术交流平台,创建于1999年,包含原创博客、精品问答、职业培训、技术论坛、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区.">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<meta name="referrer" content="always">
修改src/index.html文件(模板文件不可有注释,下列代码注释仅作解释说明,不可正式使用)
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 引入元标签 -->
<%=require('raw-loader!./meta.html')%>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script>
// 引入js静态资源
<%=require("raw-loader!babel-loader!../node_modules/lib-flexible/flexible.js")%>
</script>
</body>
</html>
2.9 多页面应用
每一次页面跳转的时候,后台服务器都会返回一个新的html,这种类型的网站也就是多页网站,也叫做多页应用。
每个页面对应一个entry、一个html-webpack-plugin
缺点:每次新增或删除页面需要改wenpack配置
利用glob.sync
- entry: glob.sync(path: path.join(__dirname, './src/*/index.js'))
安装依赖
npm i -D glob
文件夹变动,并改动相关引用文件路径。
- src/index.html=>src/index/index.html
- src/index.js=>src/index/index.js
- src/helloworld.js=>src/index/helloworld.js
- src/search.html=>src/search/index.html
- src/search.js=>src/search/index.js
修改webpack入口文件、htmlwebpackplugins配置
const glob = require('glob')
const setMPA = ()=>{
const entry = {}
const htmlWebpackPlugins = []
const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'))
Object.keys(entryFiles).map((index)=>{
const entryFile = entryFiles[index]
const match = entryFile.match(/src\/(.*)\/index\.js/)
const pageName = match && match[1]
entry[pageName] = entryFile
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/${pageName}/index.html`), // 需要打包的文件路径
filename: `${pageName}.html`,
chunks: [pageName],
inject: true, // 设置为true,js css会自动注入html
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCss: true,
minifyJs: true,
removeComments: false
}
})
)
})
return {
entry,
htmlWebpackPlugins
}
}
const { entry, htmlWebpackPlugins} = setMPA()
module.exports = {
entry: entry,
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js'
},
mode: 'production',
plugins: [
].concat(htmlWebpackPlugins)
}
2.10 使用source map
作用:运行代码与源代码之间完全不同,不方便调试应用、定位错误信息,source map就是用来映射转换之后的代码与源代码之间的关系。
开发环境开启,线上环境关闭(线上排查问题可以将sourcemap上传到错误监控系统)
webpack支持12中source-map实现方式,效率和效果也不相同。
- eval:仅能定位文件 不生成source-map
- eval-source-map:可以定位到文件、行列信息 生成source-map
- cheap-eval-source-map:只能定位到行 生成source-map
- cheap-module-eval-source-map:定位代码与源代码一模一样
- 特征:eval是否使用eval执行代码
- cheap-source-map:是否包含行信息
- module是否能够得到loader处理之前的源代码
- inline-source-map以data-url形式嵌入代码,会增大代码体积。
- hidden-source-map开发第三方包使用,并没有通过注释的方式引入,所以浏览器看不到效果。
- nosources-source-map没有源代码,但同样提供了行列信息
webpack选择合适的source-map选择
- 开发模式:cheap-module-eval-source-map
- 发布打包:不使用sourcemap,避免暴露源代码或者使用nosources-source-map
devtool: 'source-map'
2.11 提取公共页面资源
思路:比如将react、react-dom基础包通关cdn引入,不参与打包
方法:
- 使用html-webpack-externals-plugin
- 利用splitchunksplugin进行公共脚本分离
2.11.1 通过splitchunksplugin提取公共页面资源
chunks参数说明
- async同步引入的库进行分离(默认)
- initial同步引入的库进行分离
- all 所有引入的库进行分离(推荐)
test:匹配出需要分离的包
- minChunks:设置最小引用次数
- minuSize:分离包的体积大小
module.exports = {
optimization: {
splitChunks: {
minSize: 0,
cacheGroups: {
commons: {
name: "commons",
chunks: "all",
minChunks: 2
},
vendors: {
test: /(react|react-dom)/,
name: 'vendors',
chunks: 'all'
}
}
}
},
}
打包公共资源
新建common/index.js文件
2.11.2 通过html-webpack-externals-plugin提取公共页面资源
安装依赖
npm i -D html-webpack-externals-plugin
const htmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
module.exports = {
new htmlWebpackExternalsPlugin({
externals: [
{
module: 'react',
entry: 'https://11.url.cn/now/lib/16.2.0/react.min.js',
global: 'React'
},
{
module: 'react-dom',
entry: 'https://11.url.cn/now/lib/16.2.0/react-dom.min.js',
global: 'ReactDOM'
}
]
})
]
}
2.12 tree shaking 摇树优化(webpack内置)
使用 webpack默认支持,在.babelre里设置modules: false即可
production mode的情况下默认开启,测试时设置为none
要求:必须是es6的语法,CJS不支持
DCE( dead code elimination)
代码不会被执行,不可到达
代码执行的结果不会被用到
代码只会影响死变量(只写不读)
Tree-shaking原理
利用es6模块的特点:
- 只能作为模块顶层语句出现
- import的模块名只能死字符串常量
- import binding是immutable的
代码擦除:uglify阶段删除无用代码
2.13 scope hositing(webpack内置)
原理:将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突
对比:通过scope hoisting可以减少函数声明代码和内存开销
production mode的情况下默认开启
在development模式下,需要手动打开该模块
new webpack.optimize.ModuleConcatenationPlugin()
要求:必须是es6的语法,CJS不支持
2.14 代码分割和动态加载
webpack可以将代码库分割成chunks(愉快),当代码运行到需要它们的时候再进行加载。
适用的场景:
- 抽离相同代码到一个共享块
- 脚本懒加载,使得初始下载的代码更小
懒加载JS脚本的方式
- CommonJS: require.ensure
- ES6: 动态import (需要babel转换)
安装依赖
npm i -D babel-plugin-syntax-dynamic-import
修改.babelra配置,使其支持动态import
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-syntax-dynamic-import"
]
}
修改src/search/search.js文件
import React from 'react'
import ReactDOM from 'react-dom'
import '../css/index.less'
import fileImg from '../img/fileImg.png'
import '../../common/index'
class Search extends React.Component{
constructor() {
super(...arguments)
this.state = {
Text: null
}
}
loadComponent() {
import('./text.js').then((Text)=>{
console.log('Text', Text)
this.setState({
Text: Text.default
})
})
}
render() {
const { Text } = this.state
return <div className="search-txt ">search component
{Text? <Text></Text>: null}
测试字体
<img src={fileImg} onClick={()=>this.loadComponent()}></img>
</div>
}
}
ReactDOM.render(
<Search/>,
document.getElementById('root')
)
2.15 webpack和ESLint结合
方案一:webpack与CI/CD集成
方案二:webpack与ESLint集成
2.15.1 webpack与CI/CD集成
本地开发阶段增加precommit钩子
2.15.2 webpack与ESLint集成 (打包)
安装依赖
npm i -D eslint-config-airbnb eslint@^7.2.0 eslint-plugin-import@^2.22.1 eslint-plugin-jsx-a11y@^6.4.1 eslint-plugin-react@^7.21.5
npm i -D eslint-loader
npm i -D babel-eslint
修改webpack配置文件
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
'babel-loader',
'eslint-loader'
]
},
{
]
},
}
新建.eslintrc.js文件,设置eslint规则
module.exports = {
"parser": "babel-eslint", //制定解析器
"extends": "airbnb"
"env": { //JavaScript 可在文件中使用注释来指定环境
"browser": true,
"node": true
}
"rules": {
"semi": "error" //结尾应当有分号
}
}
打包,然后demo收获一堆报错……
2.16 webpack打包库和组件 terser-webpack-plugin
webpack除了可以用来打包应用,也可以用来打包js库
实现一个大整数加法库的打包,要求:
- 需要打包压缩版和非压缩版本
- 支持AMD/CJS/ESM模块引入
建立一个新的项目文件夹large-number
初始化项目,并安装依赖
npm init -y
npm i webpack webpack-cli -D
新建src/index.js文件,存放大整数加法
export default function add(a, b){
let i = a.length - 1
let j = b.length - 1
let carry = 0 // 进位
let res = ''
while ( i >= 0 || j>=0 ){
let x = 0; // a某一位的值
let y = 0; // b某一位的值
let sum
if( i >= 0 ){
x = a[i] - '0'
i --
}
if( j >= 0 ){
y = b[j] - '0'
j --
}
sum = x + y + carry
if( sum >= 10 ){
carry = 1
sum -= 10
}else {
carry = 0
}
res = sum + res
}
if( carry ){
res = carry + res
}
return res
}
修改打包文件
module.exports = {
entry: {
'large-number': './src/index.js',
'large-number.min': './src/index.js'
},
output: {
filename: '[name].js',
library: 'largeNUmber', // 打包后库的的名字
libraryTarget: 'umd', // 打包类库的发布格式,这里使用UMD
libraryExport: 'default' // 对外暴露default属性,就可以直接调用default里的属性
}
}
添加打包命令
"build": "webpack"
如果就此打包的话,'large-number'、'large-number.min'均会被压缩,需要进一步改动
npm i -D terser-webpack-plugin
修改webpack配置,设置使用环境、使用 terser-webpack-plugin插件
const terserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
mode: 'none', // 避免全部自动压缩,打包出未压缩文件
entry: {
'large-number': './src/index.js',
'large-number.min': './src/index.js'
},
output: {
filename: '[name].js',
library: 'largeNUmber', // 打包后库的的名字
libraryTarget: 'umd', // 打包类库的发布格式,这里使用UMD
libraryExport: 'default' // 对外暴露default属性,就可以直接调用default里的属性
},
optimization: {
minimize: true,
minimizer: [new terserWebpackPlugin({
include: /\.min\.js$/
})],
},
}
新建index.js文件
if(process.env.NODE_ENV === 'production'){
module.exports = require('./dist/large-number.min.js')
}else {
module.exports = require('./dist/large-number.js')
}
发布npm包
npm login
npm publish
在01project项目中使用打包
npm i -D gu-large-number
直接使用
import '../../common/index'
import largeNumber from 'gu-large-number'
export function helloworld(){
return 'hello awebpack'+largeNumber(10,20)
}
三、 webpack打包库和组件
功能模块设计和目录结构:
2.17.1 基础配置
- 资源解析
- 样式增强
- 目录清理
- 多页面打包
- 命令行打包和优化
- css提取成一个单独的文件
2.17.2 开发阶段配置
- 代码热更新
- sourcemap
通过webpack-merge
安装依赖
npm i -D webpack-merge
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
const webpack = require('webpack')
const devConfig = {
mode: 'development',
plugins:[
new webpack.HotModuleReplacementPlugin(),
],
devServer: { // 专门为webpack-dev-server指定相关配置选项
contentBase: './dist',
hot: true,
stats: 'error-only'
},
devtool: 'cheap-module-eval-source-map'
}
module.exports = merge(baseConfig, devCOnfig)
2.17.2 生产阶段配置
- 代码压缩
- 文件修改
- tree-shaking
- scope hositing
- 速度优化
- 提及优化
安装依赖
npm i -D eslint babel-eslint eslint-config-airbnb-base
新建.eslintrc.js配置文件,配置eslint规则
module.exports = {
"parser": "babel-eslint", //制定解析器
"extends": "airbnb-base"
"env": { //JavaScript 可在文件中使用注释来指定环境
"browser": true,
"node": true
}
"rules": {
// "semi": "error" //结尾应当有分号
}
}
增加运行命令
"eslint": "eslint --fix"
三、分析webpack
3.1、使用webpack内置的stats
在package.json中设置新命令
"build:stats": "webpack --config webpack.prod.js --json > stats.json",
启动,得到一个stats.json文件,着实不直观……
3.2、速度分析:使用speed-measure-webpack-plugin
可以看到每个loader和插件执行耗时
安装依赖
npm i -D speed-measure-webpack-plugin
webpack引入
const speedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
const smp = new speedMeasureWebpackPlugin()
module.exports = smp.wrap({
})
报错:
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
{
loader: 'postcss-loader',
},
{
loader: 'px2rem-loader',
options: {
remUnit: 75, // 1rem = 75px
remPrecision: 8 // 转换时的位数
}
}
]
},
ERROR in ./src/css/index.less
Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
Error: You forgot to add 'mini-css-extract-plugin' plugin (i.e. `{ plugins: [new MiniCssExtractPlugin()] }`), please read https://github.com/webpack-contrib/mini-css-extract-plugin#getting-started
at Object.pitch (/Users/gujianxiang/Demo/study/webpack/01project/node_modules/mini-css-extract-plugin/dist/loader.js:43:14)
@ ./src/search/index.js 25:0-27
暂未解决……
3.3 体积分析:使用webpack-bundle-analyzer
可以分析哪些问题?
- 依赖的第三方模块文件大小
- 业务组件大小
安装依赖
npm i -D webpack-bundle-analyzer
修改配置文件
const webpackBundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
new webpackBundleAnalyzer(),
],
}
3.3 Babel-polyfill 的作用
Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。举个栗子,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。