demo 下载地址:
demo1
webpack 初识
- 安装开发依赖 webpack webpack-cli
- 打包 npx webpack
- 支持模块化
- 打包模式 mode
demo2
webpack 打包含依赖的文件
demo3
webpack配置文件
- 脚本配置
- 文件名修改
- 打包后文件分析
配置文件名由来
打包后文件分析 自调用函数
demo4
配置文件的配置项介绍
- entry
- output
- devServer
- modules ---- loader
- plugins
- optimization
demo5
本地起服务器
- 安装:yarn add webpack-dev-server -D
- 启动: npx webpack-dev-server
- 配置服务器
webpack.config.js
devServer: {
port: 3000,
contentBase: './dist',
progress: true,
open: false,
}
- 配置执行脚本
package.json
script: {
dev: "webpack-dev-server --config webpack.config.dev.js"
}
demo6
html插件-html-webpack-plugin
yarn add html-webpack-plugin
创建模板 src/index.html
-
添加配置 webpack.config.js
- 引入插件
- plugins: []
plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', // 指定模板 filename: 'index.html', // 引入打包后js的html的文件名 minify: { removeAttributeQuotes: true, // 压缩双引号 collapseWhitespace: true, // 压缩空白行 }, hash: true, // 引用bundle.js时添加hash戳 }) ]
demo7
样式文件的打包
- CSS css-loader style-loader
- less less-loader css-loader style-loader
- sass node-sass css-loader style-loader
- stylus stylus stylus-loader
- 安装loader
- 添加配置
module: {
rules: [{
test: /\.css$/,
use: [{
loader: 'style-loader',
options: {
insert: function(element) {
var parent = document.querySelector('head');
var lastInsertedElement = window._lastElementInsertedByStyleLoader;
if (!lastInsertedElement) {
parent.insertBefore(element, parent.firstChild);
} else if (lastInsertedElement.nextSibling) {
parent.insertBefore(element, lastInsertedElement.nextSibling)
} else {
parent.appendChild(element)
}
}
},
}, 'css-loader']
},
{ test: /\.less$/, use: ['style-loader', 'css-loader', 'less-loader'] },
]
}
抽离css打包后的css样式, link标签引入 mini-css-extract-plugin
(需要自己压缩生产的css文件: 需要插件optimize-css-assets-webpack-plugin,在配置文件中添加optimization; 影响: mode: production失效,需要使用插件压缩打包后的js: uglifyjs-webpack-plugin)自动添加前缀 autoprefixer包 postcss-loader 需要配置文件 postcss.config.js
demo8
打包后样式文件的抽离,link标签的引入
- 安装插件
yarn add mini-css-extract-plugin -D
- 配置
plugins: [
new MiniCssExtractPlugin({
})
],
module: {
rules: [{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,
'css-loader'
]
},
{ test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },
]
}
打包后css文件的压缩和js文件的压缩
- 安装插件
yarn add optimize-css-assets-webpack-plugin -D
- 配置
optimization: {
minimizer: [new OptimizeCssAssetsWebpackPlugin()]
}
- 压缩js uglifyjs-webpack-plugin
yarn add uglifyjs-webpack-plugin -D
optimization: {
minimizer: [new OptimizeCssAssetsWebpackPlugin(), new UglifyjsWebpackPlugin({
cache: true,
parallel: true,
sourceMap: true,
})]
需要避免有es6语法,不然会报错
css样式自动添加前缀
- 安装npm 包 + loader autoprefixer + postcss-loader
yarn add autoprefixer postcss-loader -D
- 配置
1- 添加配置文件 postcss.config.js 告诉postcss-loader使用什么插件
// postcss.config.js
module.exports = {
plugins: [require('autoprefixer')]
}
2- 在css-loader使用前添加post-loader, 并进行插件的配置
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: loader => [require('autoprefixer')({ browsers: ['> 0.15% in CN'] })]
}
},
'less-loader'
]
},
总结:
- 抽离css打包后的css样式, link标签引入 mini-css-extract-plugin
(需要自己压缩生产的css文件: 需要插件optimize-css-assets-webpack-plugin,在配置文件中添加optimization; 影响: mode: production失效,需要使用插件压缩打包后的js: uglifyjs-webpack-plugin) - 自动添加前缀 autoprefixer包 postcss-loader 需要配置文件 postcss.config.js
demo9
babel-将es6/es7语法转化成es5语法
1- es6 语法
- 安包
yarn add babel-loader @babel/core @babel/preset-env -D
- 配置 - (es6转成es5)
{
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"]
}
}]
}
2- es6 高级语法转化成es5 ---- @babel/plugin-proposal-class-properties; @babel/plugin-proposal-decorators
- 安包
yarn add @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators -D
- 配置
{
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"],
plugins: [
['@babel/plugin-proposal-decorators', { 'legacy': true }],
['@babel/plugin-proposal-class-properties', { "loose": true }]
]
},
}]
}
3- 装饰器语法报错的解决
Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.ts(1219)
- 新增配置文件jsconfig.json
// jsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
}
}
- 重启vscode
4- 内置api 和promise的转化---- @babel/plugin-transform-runtime(必配)
- 代码
function * gen(params) {
yield 1;
}
console.log(gen().next());
- npm run dev
控制台报错
Uncaught ReferenceError: regeneratorRuntime is not defined
原因:打包对gen进行了解析, 使用到了regeneratorRuntime, 但是不会自动帮引入这个api - 安包
yarn add @babel/plugin-transform-runtime -D
yarn add @babel/runtime --save
- 配置
{
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env"],
plugins: [
['@babel/plugin-proposal-decorators', { 'legacy': true }],
['@babel/plugin-proposal-class-properties', { "loose": true }],
"@babel/plugin-transform-runtime" // 新增插件
]
},
}],
include: path.resolve(__dirname, "src"),// 查找范围
exclude: /node_modules/, // 不查找文件
}
5- 解析实例上的方法 ----
实例上的方法默认不会解析
- 代码
// a.js
"SSS".includes("S")
- npm run build
return _context.stop();\n }\n }\n }, _marked);\n}\n\nconsole.log(gen().next());\n"SSS".includes('S');\n\n//# sourceURL=webpack:///./src/a.js?");
可以看到代码没有被解析 - 安包
yarn add @babel/polyfill - 引用包
// a.js
require('@babel/polyfill');
"SSS".includes("S")
6- js添加校验 -- eslint
- 安包
yarn add eslint eslint-loader - 配置文件 .eslintrc.json 新建 (官网--DEMO-download)
- 配置 新增moudule.rules, 设计执行的顺序,强制优先执行 enforce: "pre"
{
test: /\.js$/,
use:[{
loader: 'eslint-loader',
options: {
enforce: "pre"
}
}],
},
demo10
全局变量的设置
exp: 将 $ from "jquery"挂载到window上
一、 expose-loader
1- expose-loader (全局loader/内联loader)
- 安包
yarn add expose-loader - 引用:
- 方法一 // // 这种会报错
import $ from 'expose-loader?$!jquery';
console.log(window.$, $);
// 通过属性名 "libraryName" 暴露 file.js 的 exports 到全局上下文。
// 在浏览器中,就将可以使用 window.libraryName 访问。
- 方法二
// webpack.config.dev.js
{
test: require.resolve('jquery'),
use: {
loader: "expose-loader",
options: {
exposes: {
globalName: '$'
}
}
}
},
然后正常引用
require('jquery')
console.log("window.$", window.$);
这种方式比较麻烦的是必须在每个模块中引入jquery,解决方式是推过webpack的插件在每个js文件注入$
二、 webpack.providePlugin() 在每个模块注入$,没有挂载到window上
// webpack.config.dev.js
const webpack = require('webpack');
plugins: [
new webpack.ProvidePlugin({
$: "jquery"
})
],
三、在index.html中引入cdn,并且配置不打包jquery
配置引入不打包
// webpack.config.dev.js
externals: {
jquery: "$"
}
demo11
图片处理 file-loader/ html-withimg-loader / url-loader
- 在js中创建图片引入: 需要结合import 和require 引入图片 file-loader
- css background: 不需要进行处理
- html中引入图片: html-withimg-loader
- 根据图片大小采用不同的方式处理图片 : url-loader
小于limit将图片转化成base64格式,大于使用file-loader处理
- js
import url from './images/1.png'
var img = new Image();
img.src = url;
- yarn add file-loader
- 配置
{
test: /.(png|jpg|svg|jpeg)$/,
use: 'file-loader',
}
css
不需要处理-
HTML :
使用html-withimg-loader打包html中img引入的图片,很好用,但是webpack4.x里会和html-webpack-plugin产生冲突;
原因是file-loader升级了,以前4.2的时候是没事的,现在file-loader升级到5.0了,所以需要在file-loader的options里使用一个配置:
esModule:false
这样就解决了;
- 代码
// index.html
yarn add html-withimg-loader
-
配置
rules: [{ test: /\.(png|jpg|svg|jpeg)$/, use: [{ loader: 'file-loader', options: { esModule: false } }] }, { test: /\.(html|htm)$/, use: [{ loader: 'html-withimg-loader', }] }]
- 图片打包格式
- yarn add url-loader -D
- 配置
rules: [
// {
// test: /\.(png|jpg|svg|jpeg)$/,
// use: [{
// loader: 'file-loader',
// options: {
// esModule: false
// }
// }]
// },
{
test: /\.(png|jpg|svg|jpeg)$/,
use: {
loader: 'url-loader',
options: {
esModule: false, // 不加的话会有这种情况 img属性src="[object Module]"
limit: 1024 * 100, // 当小于100kb时候生产base64
}
}
},
demo12
打包文件分类
主要解决方案是在生成资源的额时候配置路径
- CSS
plugins: [
new MiniCssExtractPlugin({
filename: 'css/main.css' // 抽离后的文件样式名
}),
],
- IMAGE
rules: [{
test: /\.(png|jpg|svg|jpeg)$/,
use: {
loader: 'url-loader',
options: {
esModule: false, // 不加的话会有这种情况 img属性src="[object Module]"
limit: 1, // 当小于100kb时候生产base64
outputPath: 'img/'
}
}
}]
引用打包的资源前面添加CDN
添加publicPath的配置
- 给所有资源添加
output: {
filename: 'bundle.[hash:8].js', // 指定每次打包时文件名含hash戳,位数是8位
path: path.resolve(__dirname, 'build'),
publicPath: "https://m.yqb.com"
},
- 给单种类型资源添加
{
test: /\.(png|jpg|svg|jpeg)$/,
use: {
loader: 'url-loader',
options: {
esModule: false, // 不加的话会有这种情况 img属性src="[object Module]"
limit: 1, // 当小于100kb时候生产base64
outputPath: '/img/',
publicPath: 'https://m.yqb.com'
}
}
},
source-map: production模式下如果代码报错无法定位,souce-map源码映射帮助代码调试
- devtool: "source-map"
单独生成一个sourcemap文件,标识出错的行列 - devtool: "eval-source-map"
不会产生单独的文件,标识出错的行列 - devtool: "cheap-module-source-map"
产生一个单独的映射文件, 不标识错误列 - devtool: "cheap-moudle-eval-source-map"
不产生文件,集成在打包后的文件中,不标识错误列
demo13
打包多页应用
修改配置 entry 和 output, 多html
entry: {
home: './src/index.js',
other: './src/other.js'
},
output: {
filename: '[name].[hash:8].js',
path: path.resolve(__dirname, 'build'),
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
filename: 'home.html',
chunks: ['home'],
minify: false,
hash: true,
}),
new HtmlWebpackPlugin({
template: './index.html',
filename: 'other.html',
chunks: ['other'],
minify: false,
hash: true,
})]
多页面应用抽离公共代码
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
priority: 1, // 优先抽取
test: /[\\/]node_modules[\\/]/,
chunks: 'initial',
// minSize: 0, // 公共代码大小超过0字节
// minChunks: 1, // 使用次数超过1次
},
common: {
chunks:'initial',
minSize: 0,
minChunks: 2,
},
}
},
watch 的使用----实现实时打包
// webpack.config.js
watch: true,
watchOptions: {
poll: 1000, // 每秒问我1000次
aggregateTimeout: 500, // 防抖,停止输入代码500s后打包文件
ignored: /node_modules/, // 不需要监控哪个文件
}
webpack的小插件的应用
- clean-webpack-plugin 需安装: 新打包前删除打包文件夹如build, 参数为空或对象
yarn add clean-webpack-plugin --save-dev
// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
new CleanWebpackPlugin(),
],
- copy-webpack-plugin 需安装: 拷贝文件到打包后文件夹 参数: 数组 + 对象
yarn add copy-webpack-plugin --save-dev
// webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
plugins: [
new CopyWebpackPlugin({
patterns: [{
from: './doc',
to: './doc' // 在build文件夹下的路径
}]
}),
],
- bannerPlugin 内置 需要引入webpack: 在打包后的js文件中插入指定字符串 参数: 字符串
const webpack = require('webpack');
plugins: [
new webpack.BannerPlugin('我是summer 2020.07.23')
],
demo14
跨域
- 代理 http-proxy
// webpack.config.js
devServer: {
proxy: { // 请求8080端口以/api开始的接口重定向到3000端口
"/api": {
target: "http://localhost:3000",
pathRewrite: {
'/api': '',
}
}
}
},
- 前端mock数据
// index.js
let xhr = new XMLHttpRequest();
xhr.open('GET', "/user", true);
xhr.onload = function() {
console.log(xhr.response);
}
xhr.send()
// webpack.config.js
devServer: {
before(app) {
app.get('/user', (req, res) => {
res.json({ name: 'before 3000端口' })
})
},
},
- 服务端启动webpack(有服务端不用代理,端口使用服务端端口)webpack-dev-middleware
yarn add webpack-dev-middleware -D
// server.js
const express = require('express');
let app = express();
const webpack = require('webpack');
// 中间件
const middle = require('webpack-dev-middleware');
const config = require('./webpack.config.dev.js');
const complier = webpack(config);
app.use(middle(complier))
app.get('/user', (req, res) => {
res.json({ name: 'express 3000端口' })
})
app.listen(3000);
resolve
解析第三方模块
resolve: {
modules: [path.resolve('node_modules')], // 指定只查找当前目录下的node_modules
// mainFields: ['style', 'main'], // 指定查找顺序
// mainFiles: [], // 指定入口文件
// alias: { // 别名
// bootstrap: 'bootstrap/dist/css/bootstrap.css'
// }
extensions: ['.js', '.css', '.json'] // 文件省略后缀的情况下的查找顺序
},
定义环境变量 (区分开发和产线环境)webpack 内置插件 definePlugin
const webpack = require('webpack');
plugins: [
new webpack.DefinePlugin({
DEV: JSON.stringify('production'),
FLAG: 'true',
})
],
区分不同环境 webpack-merge
yarn add webpack-merge -D
// webpack.config.js
const commonConfig = { ... };
const productionConfig = { ... };
const developmentConfig = { ... };
module.exports = env => {
switch(env) {
case 'development':
return merge(commonConfig, developmentConfig);
case 'production':
return merge(commonConfig, productionConfig);
default:
throw new Error('No matching configuration was found!');
}
}
demo15
webpack 的优化
yarn add webpack webpack-cli html-webpack-plugin @babel/core babel-loader @babel/preset-env @babel/preset-react webpack-dev-server -D
noParse: 不去解析指定库中的依赖关系,提高打包速度
使用场景: 提前知道第三方库没有依赖
module: {
noParse: /jquery/, // 不去解析jquery中的依赖库
},
IgnorePlugin: webpack内置插件--忽略掉第三方插件的某些内容,手动实现按需引用
运用场景: moment第三方插件,语言包的引用
package.json: main:moment.js -> aliasedRequire('./locale/' + name);
引入locale,所有语言包
- 没配置前打包文件大小
Version: webpack 4.44.0
Time: 2231ms
Built at: 2020-07-27 7:12:16 AM
Asset Size Chunks Chunk Names
main.js 1.37 MiB main [emitted] main
Entrypoint main = main.js
- 配置
const webpack = require('webpack');
module.exports= {
plugins: [
new webpack.IgnorePlugin(/\.\/locale/, /monent/)
],
}
- 配置后打包文件大小
Version: webpack 4.44.0
Time: 2425ms
Built at: 2020-07-27 7:42:21 AM
Asset Size Chunks Chunk Names
bundle.1956a6f4.js 862 KiB main [emitted] [immutable] main
index.html 308 bytes [emitted]
Entrypoint main = bundle.1956a6f4.js
dllPlugin---动态链接库 DllReferencePlugin + DllPlugin webpack内置插件
应用场景: 打包时不打包react, react-dom
yarn add react react-dom
目的:不希望在开发热更新时重复打包第三方模块,否则速度太慢。
打包完第三方依赖后,就要去打包业务代码,这个时候就需要让业务代码知道不要再去打包哪些第三方模块了
直接从打包好的__dll_react.js里面去取就可以了
在这里打包第三方依赖的时候,生成一份说明文件manifest.json,来让webpack在打包业务代码的时候
知道打包哪些模块需要从__dll_react.js里面取而不是重新打包
总结整体流程:
通过dllPlugin生成manifets.json和_dll_react.js,vendor.js会自执行返回一个加载函数_dll_react(名字可配置),通过闭包将模块存储在内存中,注意_dll_react是一个全局变量。
webpack通过DllReferencePlugin在打包的时候分析业务代码中使用了哪些第三方模块,哪些模块是不需要打包进业务代码中,而是去_dll_react.js中获取。
vendor中获取的模块是通过调用全局函数_dll_react(id)来进行引入。
happypack --- 使用多线程打包
yarn add happypack
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const Happypack = require('happypack')
module.exports = {
plugins: [
new Happypack({
id: 'js',
use : [{
loader: 'babel-loader',
options: {
presets: ["@babel/preset-env", '@babel/preset-react'],
},
}],
})
],
module: {
rules: [{
test: /\.js$/,
use: 'Happypack/loader?id=js',
include: path.resolve(__dirname, "src"), // 查找范围
exclude: /node_modules/, // 不查找文件
}],
},
}
webpack自带优化
- tree-shaking
默认情况下在production模式下, import 会自动去除掉没用的代码 tree-shaking
commonjs模块会把结果放到default上
结论:建议使用import 语法 - scope hosting
会自动省略一些可以简化的代码: 比如简单的表达式计算
抽离公共代码
使用场景:多页应用引入公共模块
- 抽离自定义公共文件
demo13
optimization: {
splitChunks: { // 分割代码块
cacheGroups: { // 缓存组
common: {chunks: 'initail',
minSize: 0, // 公共代码大小超过0字节
minChunks: 1, // 使用次数超过1次
}
}
}
- 抽离第三方模块
optimization: {
splitChunks: { // 分割代码块
cacheGroups: { // 缓存组
vendors: {
priority: 1, // 优先抽取
test: /node_modules/,
chunks: 'initail',
minSize: 0, // 公共代码大小超过0字节
minChunks: 1, // 使用次数超过1次
}
}
}
chunks:表示从哪些chunks里面抽取代码,除了三个可选字符串值 initial、async、all 之外,还可以通过函数来过滤所需的 chunks;
minSize:表示抽取出来的文件在压缩前的最小大小,默认为 30000;
maxSize:表示抽取出来的文件在压缩前的最大大小,默认为 0,表示不限制最大大小;
minChunks:表示被引用次数,默认为1;上述配置commons中minChunks为2,表示将被多次引用的代码抽离成commons。
懒加载的实现 ----import语法
let button = document.createElement('button');
button.innerHTML = "hello"
button.addEventListener('click', function () {
// jsonp实现动态加载文件
import('./test.js').then(data=> {
console.log(data);
console.log(data.default);
})
})
document.body.appendChild(button)
热更新
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
devServer: {
contentBase: './build',
hot: true, // 启用热更新
},
plugins: [
new webpack.NamedModulesPlugin(), // 打印更新的模块路径
new webpack.HotModuleReplacementPlugin() // 热更新插件
],
}
// index.js
if(module.hot){ // 如果当前文件支持热更新,调用accept监听test文件的热更新
module.hot.accept('./test.js', function () {
console.log('文件更新了');
let str = require('./test.js')
console.log(str);
})
}