安装
- 全局安装(不推荐)
npm i webpack webpack-cli -g webpack -v npm uninstall webpack webpack-cli -g - 局部安装
npm i webpack webpack-cli --save-dev npm info // 查看历史发布信息 npm install webpack@xx.xx webpack-cli -S //安装指定的版本
配置、结构
webpack有默认的配置的文件wenpack.config.js,我们可以在这里进行修改和个性化配置
npx webpack //执行命令后,webpack会找到默认的配置文件,并使用执行
npx webpack --config customerConfig.js // 指定配置文件
// package.json
sciprts:{
"dev": "webpack"
}
npm run dev
原理就是模块局部安装会在node_modules/.bin目录下创建一个软连接
module.exports = {
entry: './src/main.js' // 打包入口文件
output: './dist',//输出结构
mode: 'production',//打包模式 prod/dev
module:{
rules:[
// loader模块处理
{
test: /.css$/,
use: 'style-loader'
}
]
},
//插件配置
plugins: [
new HtmlWebpackPlugin()
]
}
webpack 核心模块
entry
指定webpack打包的入口文件,webpack执行构建的第一步将从entry开始
{
// 单入口
entry: {
main: './src/main.js'
},
entry: './src/main.js',
// 多入口
entry:{
main: './src/main.js',
index: './src/index.js'
}
}
output
打包转换后的文件输出位置
output:{
filename: 'bundle.js', //输出文件的名称
filepath: path.resolve(__dirname,'dist') // 必须是绝对路径
}
// 多入口处理
output:{
filename: '[name][chunkhash:8].js',//文件名称
filepath: path.resolve(__dirname,'dist)
}
loader
模块解析,把模块内容按照需求转换成新内容,webpack是模块打包,模块不仅仅是js,还可以是css、图片,但是webpack默认只知道处理js、json格式的,那么其他格式的模块处理就需要其他loader
style-loader、css-loader
less-loader、sass-loader
ts-loader
babel-loader//转换ES6、7等js新特性语法
file-loader
eslint-loader
module
当webpack处理不认识的模块时,需要在webpack中的module处进⾏ 配置,当检测到是什么格式的模块,使⽤什么loader来处理
module:{
rules:[
{
test: /.(svg|png|jpe?g|gif)/$,
use: {
loader: 'file-loader',
options:{
name:'[name]_[hash].[ext]',
// 打包后存放的位置
outputPath: "images/",
publicPath: "../images"
}
}
}
]
}
url-loader内部使⽤了file-loader,所以可以处理file-loader所有的事 情,但是遇到jpg格式的模块,会把该图⽚转换成base64格式字符串, 并打包到js⾥。对⼩体积的图⽚⽐较合适,⼤图⽚不合适。
{
...
limit: 2048 // 小于2048转换成base64
}
plugins
plugin 可以在webpack运⾏到某个阶段的时候,帮你做⼀些事情,类似于 ⽣命周期的概念 扩展插件,在 Webpack 构建流程中的特定时机注⼊扩展逻辑来改变构建结 果或做你想要的事情。 作⽤于整个构建过程
HtmlWebpackPlugin,
clean-webpack-plugin
mini-css-extract-plugin
sourceMap
webpackDevServer
ES6的处理
babel-loader是webpack 与 babel的通信桥梁,不会做把es6转成es5的
⼯作,这部分⼯作需要⽤到@babel/preset-env来做//@babel/preset-env⾥包含了es6转es5的转换规则。
还不够,Promise等⼀些还有转换过来,这时候需要借助
@babel/polyfill,把es的新特性都装进来,来弥补低版本浏览器中缺失的特性
polyfill造成全局污染
plugin-transform-runtime 闭包方式(开发组件库、工具库)
webpack 优化
treeShaking
//去掉没有引⽤的代码
//webpack.config.js
optimization:{
usedExports: true
}
//package.json
"sideEffects": false//正常对所有模块进行tree shaking
"sideEffects":["*.css","@babel/polyfill"]
开发模式下,不会去掉没有引用的代码
code Splitting
optimization: {
splitChunks: {
chunks: 'async',//对同步 initial,异步 async,所有的模块有效 all
minSize: 30000,//最⼩小尺⼨寸,当模块⼤大于30kb
maxSize: 0,//对模块进⾏行行⼆二次分割时使⽤用,不不推荐使⽤用
minChunks: 1,//打包⽣生成的chunk⽂文件最少有⼏几个chunk引⽤用了了这个模块
maxAsyncRequests: 5,//最⼤大异步请求数,默认5
maxInitialRequests: 3,//最⼤大初始化请求书,⼊入⼝口⽂文件同步请求,默认3
automaticNameDelimiter: '~',//打包分割符号
name: true,//打包后的名称,除了了布尔值,还可以接收⼀一个函数
function
cacheGroups: {//缓存组
vendors: {
test: /[\\/]node_modules[\\/]/,
name:"vendor", // 要缓存的 分隔出来的 chunk 名称
priority: -10//缓存组优先级 数字越⼤大,优先级越⾼高
},
other:{chunks: "initial", // 必须三选⼀一: "initial" |"all" | "async"(默认就是async)
test: /react|lodash/, // 正则规则验证,如果符合就提取
chunk,
name:"other",
minSize: 30000,
minChunks: 1,
},
commons:{
test:/(react|react-dom)/,
name:"react_vendors",
chunks:"all"
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true//可设置是否重⽤用该chunk
}
}
}
optimization:{
//帮我们⾃动做代码分割
splitChunks:{
chunks:"all",//默认是⽀持异步,我们使⽤all
}
}
DllPlugin
//package.json
{
"scripts":{
"dll": "webpack --config webpack.dll.config.js",
}
}
// webpack.dll.config.js
{
entry: {
vendor:["react","react-dom",...]
},
output:{
path: path.join(__dirname,"./bundle"),
filename:[name][hash:8].dll.js,
library:[name]_library
},
plugins:[
new webpack.DllPlugin({
context: __dirname,
name:[name]_library,
path: path.join(__dirname,"./[name].mainfest.json")
})
]
}
//webpack.config.js
{
plugins:[
new webpack.DllReferencePlugin({
manifest: path.resole(__dirname, "vendor.mainfest.json")
})
]
}
HMR:热模块替换
devServer:{
contentBase: './dist',
open: true,
hot: true,
hotOnly: true
}
const webpack = require('webpack')
plugins:[
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin()
]
// index.js
import "@/style/index.css"
let btn = document.createElement('button')
btn.innerHTML = '按钮'
document.body.appendChild(btn)
处理理js模块HMR
module.hot.accept来观察模块更更新 从⽽而更更新
//counter.js
export function counter(){
let div = document.createElement('div')
div.innerHTML = 'hello world'
document.body.appendChild(div)
}
export function number(){
let div = document.createElement('div')
div.innerHTML = 123
document.body.appendChild(div)
}
//index.js
import {counter, number} from './counter'
number()
if(module.hot){
module.hot.accept('./b',function(){
document.body.removeChild(document.getElementById('number'))
number()
})
}
webpack打包原理分析
(function(modules) {
installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
});
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
module.l = true;
return module.exports;
}
return __webpack_require__((__webpack_require__.s ="./index.js"));
})({
"./index.js": function(module, exports) {
eval(
'// import a from "./a";\n\nconsole.log("hello
word");\n\n\n//# sourceURL=webpack:///./index.js?'
);
}
});
webpack_require 来实现模块化,代码都缓存在installedModules⾥,代码⽂文件以对象传递进来,key
是路路径,value是包裹的代码字符串串,并且代码内部的require,都被替换
成了webpack_require
实现一个bundle.js 读取入口文件,分析代码
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require("@babel/traverse").default;
const babel = require('@babel/core')
const md = filename => {
const content = fs.readFileSync(filename, 'utf-8')
//解析成语法书
const ast = parser.parse(content, {sourceType: 'module'})
// 拿到依赖
const dependencies = {}
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(filename)
const newFIle = './' + path.join(dirname, node.source.value)
dependencies[node.source.value] = newFile
}
})
const {code} = babel.transformFromAst(ast,null,{
presets: ["@babel/preset-env"]
})
return {
filename,
dependencies,
code
}
}
<!--{-->
<!-- filename: 'src/index.js'// 入口文件-->
<!-- dependencies:{'./a.js','./src/a.js'},// 引入路径,项目中路径-->
<!-- code: ''// 浏览器可执行的代码-->
<!--}-->
如何编写一个loader
loader就是一个函数,生命是函数,不能用箭头函数,拿到源代码,做进一步的修饰处理,返回处理完成的源码
//customerLoader.js
const loaderUtils = require('loader-utils')//推荐处理loader/query的工具
module.exports = function(source){
// 注意这里的this
const options = loaderUtils.getOptions(this)
//return source.replace(/-_-!!!/,'')
//使用callback可以返回多个信息
this.callback(err:Error| null,content,)
//对于异步的处理使用this.async,他会返回this.callback
const callback = this.async
settimeout(()=>{
const options = loaderUtils.getOptions(this)
callback(null,result)
},100)
}
//webpack.config.js
rules:[
{
test:/\.js$/,
use: [
{
loader:path.resolve(__dirname,'customerLoader.js'),
options:{...}
}
]
}
]
........................
创建一个plugins
plugin开始打包,在某个时刻,帮助我们处理一些事情 plugin是一个类,里面包含一个apply函数,接受一个参数compiler
class CopyPlugin {
constructor(options){
//options 参数
}
// compiler 就是webpack实例
apply(compiler){
// hoods.emit 定义在某个时刻(生命周期)
compiler.hooks.emit.tapAsync("CopyPlugin",(compilation,cb)=>{
...
console.log(异步写法)
cb()
})
compiler.hooks.emit.tapAsync('CopyPlugin',compilation => {console.log('同步写法')})
}
}
module.exports = CopyPlugin
// webpack.config.js
const CopyPlugin = require('./copy-plugin')
plugins:[
new CopyPlugin({
name: '测试'
})
]
compiler-hooks:https://webpack.js.org/api/compiler-hooks/
打包速度
文件编译、文件打包很影响打包的效率的, happypack
粘贴粘的好累-_-!!!!