一 何为webpack
01 环境搭建
安装nvm curl -o- raw.githubusercontent.com/creationix/… | bash 把nvm填加到环境变量 source ~/.bash_profile nvm的常用命令 :(反正我也记不住)
- nvm install stable ## 安装最新稳定版 node
- nvm install ## 安装指定版本
- nvm uninstall ## 删除已安装的指定版本,语法与install类似
- nvm use ## 切换使用指定的版本node
- nvm ls ## 列出所有安装的版本
- nvm ls-remote ## 列出所有远程服务器的版本(官方node version list)
- nvm current ## 显示当前的版本
- nvm alias ## 给不同的版本号添加别名
- nvm unalias ## 删除已定义的别名
- nvm reinstall-packages ## 在当前版本 node 环境下,重新全局安装指定版本号的 npm 包
- nvm 所有命令 自行logo
何为 webpack
其最核心的功能是解决模块之间的依赖。 在以前引入多个script文件到页面中,有很多缺点:
- 需要手动维护js的加载顺序
- 每个script标签都是一次请求
- 在每个script中,顶层作用域即全局作用域,会造成全局作用域污染 模块化解决了上述所有问题。
- 通过导入和导出语句可清晰看到模块的依赖关系
- 模块可借助工具打包,合并资源文件,减少了网络开销
- 多个模块之间是作用域隔离的,彼此不会有命名冲突
02 以node规范创建项目
npm init -y package.json
{
"private": true, // 私有项目
// ...
}
工程源代码放在/src中,输出资源放在/dist中。
在项目目录中直接运行webpack -v是无法找到webpack的
运行npx webpack -v会找到项目目录中node_modules里的webpack
npx webpack --config wepback.dev.js
npm i --save-dev是将npm包作为(devDependencies)开发环境依赖。假如上线时要进行依赖安装,可通过npm i --produciton过滤掉devDependencies中的冗余模块,从而加快安装和发布的速度。
03 Mode
Mode用来指定当前的构建环境是: production、development 还是 none 设置mode可使用webpack内置的函数,默认值为production
Mode内置函数功能
| 选项 | 描述 |
|---|---|
| development | 设置process.env.NODE_ENV 的值为 development . 开启NamedChunksPlugin 和 NamedModulesPlugin |
| production | 设置process.env.NODE_ENV 的值为 production .开启FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin |
| none | 不开启任何优化选项 |
04 performance 性能相关错误
{ performance: false // 不提示性能相关错误 }
二 模块打包
模块之于程序,细胞之于生物体
01 commonJS
var module = {...}
module.exports = {...}
commonJS也支持简化的导出方式
exports.name = 'calculater'
exports.add = function(a, b){
return a + b;
}
exports.add相当于在module.exports的简写。不要直接给exports赋值,否则会导致其失效。
exports = {
name: 'calculater' // 失效
}
02 ES6 Module
在ES6 Module中不管开头是否有 use strict 都会采用严格模式 在ES6 Module 中使用 export命令来导出模块
导出
- 命名导出
export const name = "calculater"
export const add = function(a, b){return a + b}
或
const name = "calculater"
const add = function(a, b){return a + b}
export {name, add}
export {name, add as getSum} // 通过as关键字对变量重命名, 导入时即为 name 和 getSum
- 默认导出
export default {
name: "calculater",
add: function(a, b){return a + b}
}
导入
可通过 as 对导入的变量重命名
import {name, add as caculateSum } from './calculator.js'
导入多个变量时可通过 * as 整体导入。
import * as calculator from './calculator.js'
一个混合导入方式
import React, {Component} from 'react'
03 CommonJS 和 ES6 Module区别
动态与静态
CommonJS是动态的,ES6 Module是静态的。这里的“动态”含义是,模块依赖关系的建立发生在代码运行阶段:而“静态”则是模块依赖关系的建立发生在代码编译阶段。 ES6的编译阶段可分析出模块的依赖关系。相比CommonJS具备以下优势:
- 死代码检测和排除
- 模块变量类型检查
- 编译器优化
值拷贝与动态映射
在导入一个模块时,CommonJS是导出值的拷贝:而在ES6 Module中则是值的动态映射且只读。 CommonJS的值拷贝 calculator.js
var count = 0;
module.exports = {
count: count,
add: function(a,b){count += 1; return a + b}
}
index.js
var count = require('calculator.js')
var add = require('calculator.js')
console.log(count); // 0
add(2,3)
console.log(count); // 0 calculator.js中变量的改变不会对这里的拷贝值造成影响
count += 1;
console.log(count); // 1 拷贝值可变
ES6 Module的映射 calculator.js
let count = 0;
conts add = function(a,b){count += 1; return a + b}
export {count, add}
index.js
import {count, add} from './calculator.js'
console.log(count); // 0
add(2,3)
console.log(count); // 1 实时反映calculator.js中count值的变化
count += 1; //不可更改,会抛出SyntaxError:"count" is read-only
三 资源输入输出
webpack会从入口文件刊检索,并将具有依赖关系的模块生成一棵依赖树,最终得到一个chunk。由这个chunk得到的打包产物我们一般称为bundle。
01 context
context可理解为资源入口的路径前缀,在配置时要求必须绝对路径。
module.exports = {
context: path.join(__dirname, './src'),
entry: './scripts/index.js'
}
module.exports = {
context: path.join(__dirname, './src/scripts'),
entry: './index.js'
}
配置context主要目的是让entry简洁,尤其在多入口时。可省略,默认为当前工程根目录
02 Entry
entry可多种形式:字符串、数组、对象、函数
数组会将多个资源预先合并,在打包时会将数组中最后一个元素作为实际入口
module.exports = {
entry: ['babel-polyfill', './src/index.js']
}
上面等价于
// webpack.config.js
module.exports = {
entry: './src/index.js'
}
//index.js
import 'babel-polyfill'
对象类型入口
多入口必须使用对象形式
module.exports = {
entry: {
index: './src/index.js',
lib: './src/lib.js',
admin: ['babel-polyfill', './src/index.js']
}
}
在使用字符串或数组定义单入口时,并没有办法更改chunk name,只能为默认的"main"
函数类型入口
只要返回上面介绍的任何配置即可,优点在于可在函数体里添加动态逻辑也可返回一个promise
module.exports = {
entry: ()=> new Promise((resolve)=>{
setTimeout(()=>{
resolve('./src/index.js')
}, 1000)
})
}
03 实例
单页应用(SPA)一般定义单一入口即可
module.exports = {
entry: './src/app.js'
}
这样的好处是只会产生一个JS文件,依赖关系清晰。弊端是所有模块都打包到一起,大型项目体积过大,降底了页面渲染速度。
在webpack默认配置中,当一个bundle大于250kb时(压缩前)会认为bundle过大,在打包时会发出警告。
多页面打包基本思路
每个页面对应一个entry,一个html-webpack-plugin 缺点:每次新增或删除页面需要改webpack配置
解决方案:动态获取entry和设置html-webpack-plugin数量
webpack.config.js
const makePlugins = (configs) => {
const plugins = [
//...
]
Object.keys(config.entry).forEach((item)=>{
plugins.push(
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/${pageName}/index.html`),
filename: `${item}.html`,
chunks: ['runtime', 'vendors', `${item}`] // 需要的chunks
})
)
})
return plugins;
}
const configs = {
entry: {
index: './src/index.js',
admin: './src/admin.js',
lib: './src/lib.js'
}
}
configs.plugins = makePlugins(configs)
03 Output
filename
- 输出资源的文件名
- 还可以是一个相对路径,即使路径目录不存在也没关系,webpack会创建该目录
- 多入口场景中,需要为对应产生的每个bundle指定不同的名字
module.exports = {
output: {
filename: 'bundle.js', // 1.
filename: './js/bundle.js', // 2.
filename: '[name].js' // 3.
}
}
path 可指定资源输出的位置,要求必须为绝对路径
module.exports = {
output: {
path: path.join(__dirname, 'dist')
}
}
webpack4后,output.path默认为dist目录,可不配置
publicPath
同path不同,path用来指定资源的输出位置,publicPath用来指定资源的请求位置。
- 输出位置:打包完成后资源产生的目录,一般为dist
- 请求位置:由JS或CSS所请求的间接资源路径。如异步加载的JS,从CSS请求图片字体等。publicPath就是指定成部分间接资源的请求位置。 publicPath有三种形式
- HTML相关: 可将publicPath指定为HTML的相对路径,在请求这此资源时会以从前页面HTML所有路径加上相对路径,构成实际请求的URL
// 假设当前HTML地址为:https://abc.com/app/index.html
// 异步加载资源名为0.chunk.js
publicPath: "" // https://abc.com/app/0.chunk.js
publicPath: "./js" // https://abc.com/app/js/0.chunk.js
publicPath: "../assets/" // https://abc.com/assets/0.chunk.js
- Host相关: 若publicPath以'/'开始,则代表以当前页面的host name为基础路径
// 假设当前HTML地址为:https://abc.com/app/index.html
// 异步加载资源名为0.chunk.js
publicPath: "/" // https://abc.com/0.chunk.js
publicPath: "/js" // https://abc.com/js/0.chunk.js
publicPath: "/dist/" // https://abc.com/dist/0.chunk.js
- CDN相关:以协议头或相对协议形式开始
// 假设当前HTML地址为:https://abc.com/app/index.html
// 异步加载资源名为0.chunk.js
publicPath: "http://cdn.com" // http://cdn.com/0.chunk.js
publicPath: "https://cdn.com" // https://cdn.com/0.chunk.js
publicPath: "//cdn.com/assets/" // cdn.com/assets/0.chunk.js
WebpackDevServer中也有一个publicPath,这个publicPath与webpack中的配置含义不同,它的作用是指定WebpackDevServer的静态资源服务路径。 为了避免开发环境和生产环境产生不一致的疑惑,可将WebpackDevServer中的publicPath 与 webpack中的output.path保持一致。
module.exports = {
output: {
path: path.join(__dirname, 'dist')
},
devServer: {
publicPath: '/dist/'
}
}
04 文件指纹如何生成
- Hash: 和整个项目的构建相关,只要项目文件有修改,整个项目构建的hash值就会更改(A页面修改,会影响B页面的hash)
- Chunkhash: 和webpack打包的chunk有关,不同的entry会生成不同的chunkhash值(A页面修改,不会影响B页面/A页面修改JS,会影响A页面CSS的hash)
- Contenthash: 根据文件内容来定义hash,文件内容不变,则contenthash不变(A页面修改JS,不会修改A页面的CSS)
JS的文件指纹设置
设置output的filename, 使用[chunkhash]
module.exports={
output:{
filename:'[name][chunkhash:8].js', //对于JS使用chunkhas
},
plugins:[
new MiniCssExtractPlugin({ // 提取css到一个独立的文件
filename:'[name][countenthast:8].css' // 对于css使用countenthash
})
],
module:{
rules:[
{
test:/\.(png|svg|jpg|gif)$/,
use:[
{
loader: 'file-loader',
options:{
name: 'img/[name][hash:8].[ext]' //此处hash和js里的hash不同
}
}
]
}
]
}
}
| 占位符 | 含义 |
|---|---|
| [ext] | 资源后缀名 |
| [name] | 文件名称 |
| [path] | 文件的相对路径 |
| [folder] | 文件所在的文件夹 |
| [contenthash] | 文件的内容hash,默认是md5生成 |
| [hash] | 文件内容的hash,默认是md5生成 |
| [emoji] | 一个随机的指代文件内容的emoj |
四 Loaders
webpack开箱即用只支持JS和JSON两种文件类型,通过Loaders去支持其它文件类型并且把它们转化成有效的模块,并且可添加到依赖图中。 Loaders本身是一个函数,接受源文件作为参数,返回转换的结果。
基本配置项
- test 可接收一个正则或一个元素为正则的数组
- use可接收一个数组,数组包含该规则所使用的loader
- exclue与include是用来排除或包含指定目录下的模块,可接收正则或字符串(文件绝对路径),以及由它们组成的数组。 两者同时存在时exclude优先级更高,正由于此,可对include中的子目录进行排除
rules: [
{
exclude: /src/lib/, //排除src中的lib目录
include: 'src'
}
]
- resoure与issuer,可用于更加精确地确定模块规则的作用范围 在webpack中,被加载模块是resource,加载者是issuer 前面介绍的test/exclude/include本质上属于对resource也就是被加载者的配置,想要对加载者配置使用issuer
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
exclude: /node_modules/,
issuer: {
test: \.js$/,
include: /src/pages/
}
}
]
上面等价于
rules: [
{
use: ['style-loader', 'css-loader'],
resource: {
test: /\.css$/,
exclude: /node_modules/,
},
issuer: {
test: \.js$/,
include: /src/pages/
}
}
]
- enforce 用来指定一个loader的种类,只接收 "pre" 或 "post"
常见Loaders
| 名称 | 描述 |
|---|---|
| babel-loader | 转换ES6、7等JS新特性语法 |
| css-loader | 支持.css文件的加载和解析 |
| less-loader | 将less文件转换成css |
| ts-loader | 将TS转换为js |
| file-loader | 进行图片、字体等的打包 |
| raw-loader | 将文件以字符串的形式导入 |
| thread-loader | 多进程打包JS和CSS |
01 babel
npm i -D babel-loader @babel/core @babel/preset-env
- babel-loader 是使用babel与webpack协同工作的模块
- babel/core 是babel的核心模块
- babel/preset-env 根据用户设置的目标环境自动添加所需的插件和补丁来编译es6代码
reles: [
{
test:/\.js$/,
exclude: /node_modules/, // 必配项忽略node_modules目录
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true, // 启用缓存,优化打包速度
presets: [[
'env', {
modules: false // 禁用模块语句的转化(见下面说明)
}
]]
}
}
}
]
babel/berset-env会将ES6 module转化为CommonJS形式,这会导致tree-shaking失效 将babel/presets-env设置为false会禁用模块语句的转化,将ES6 module语法交给webpack处理
module.exports = {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options:{
presets: [["@babel/preset-env", {
useBuiltIns: 'usage', // 只引入用到的polyfill
targets: {
chrome: '67'
}
}]]
}
}]
}
npm i -S @babel/polyfill 在所有代码运行前要 import '@babel/polyfill', 但是polyfill可优化CDN引入
打包UI库时,配置不同,import '@babel/polyfill' 会污染全局变量,需删除,然后如下配置 npm i -D @babel/plugin-transform-runtime npm i -S @bable/runtime @babel/runtime-corejs2
module.exports = {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options:{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
}]
}
babel 配置会很长,可创建.babelrc
{
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"corejs": 2, // 只引入用到的polyfill
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
babel 打包 React
npm i -S @babel/preset-react
.babelrc
{
presets: [
[
"@babel/preset-env", {
useBuiltIns: 'usage', // 只引入用到的polyfill
targets: {
chrome: '67',
}
}
],
"@babel/preset-react"
]
}
02 TypeScript 配置
npm i -S @types/lodash (在TS中使用lodash) import * as _ from 'lodash'
webpack.config.jsx
module.exports = {
mode: 'production',
entry: './src/index.tsx',
module: {
rules: [{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}
tsconfig.json
{
"compilerOptions":{
"outDir": "./dist",
"module": "es6",
"target": "es5",
"allowJs": true
}
}
03 html-loader
将HTML文件转化为字符串并进行格式化,这使得我们可把一个HTML片段通过js加载进来 npm i html-loader
{
test: /\.html$/,
use: 'html-loader'
}
五静态资源
resolve.alias创建的alias不仅可在JS中使用,在html和css中也可使用
resolve:{
alias: {
'@assets':path.resolve(__dirname, './src/assets')
}
}
.bg-img{ background: url(~@assets/img/small.png) no-repeat}
<img src="~@assets/img/foo/bar.png"/>
Tips: HTML使用<img>引入图片等静态资源时,需要添加html-loader配置,不然不会处理静态资源路径问题。
01 file-loader
用于打包文件类型的资源,并返回其publicPath
file-loader引入图片
import avatar from './avatar.png'
var img = new Image()
img.src = avatar;
var root = document.getElementById('root')
root.append(img)
webpack.config.js
module.exports = {
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist'),
publicPath: './assets/'
},
module: {
rules: [
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'file-loader',
options:{
name:'[name]_[hash].[ext]',
outputPath: './another-path/',
// 打包到dist目录下哪里会覆盖output.publicPath
}
}
}
]
}
}
output.path是资源的打包输出路径,output.publicPath是资源引入路径。
02 url-loader
与file-loader类似,不同在于可设置一个文件大小的阀值,大于该阀值时与file-loader一样返回publicPath,小于时返回文件base64形式编码。
module.exports = {
module: {
rules: [
{
test: /\.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options:{
name:'[name]_[hash].[ext]',
outputPath: 'images/',
limit: 2048 // 图片小于2048kb以base64插入到代码里,否同file-loader
}
}
}
]
}
}
六 插件
插件用于bundle文件的优化,资源管理和环境变量注入 作用于整个构建过程,增强webpack
| 名称 | 描述 |
|---|---|
| CommonsChunkPlugin | 将chunks相同的模块代码提取成公共JS |
| CleanWebpackPlugin | 清理构建目录 |
| ExtractTextWebpackPlugin | 将CSS从bunlde文件里提取厂一个独立的css文件 |
| CopyWebpackPlugin | 将文件或文件夹考贝到构建的输出目录 |
| HtmlWebpackPlugin | 创建html文件去承载输出的bundle |
| UglifyisWebpackPlugin | 压缩js |
| ZipWebpackPlugin | 将打包出的资源生成一个zip包 |
01 html-webpack-plugin
会在打包结束后在dist生成html文件并把bondle.js引入到html中 npm i html-webpack-plugin -D
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,
minify:{ // 压缩配置
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
]
}
02 clean-webpack-plugin
清除dist目录
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin(['dist'])
]
}