一、webpack简介
webpack is a static module bundler for modern JavaScript applications.
1.1 介绍
- webpack是一个静态的模块化打包工具,为现代的JavaScript应用程序
-
打包bundler:webpack可以进行打包,所以它是一个打包工具
-
静态的static:可以将代码打包成最终的静态资源(部署到静态服务器)
-
模块化module:webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等
-
现代的modern:因为现代前端开发面临各种各样的问题,才催生了webpack的出现和发展
-
- 加载vue项目为例:
-
JavaScript的打包:
- 将ES6转换成ES5的语法
- TypeScript的处理,将其转换成JavaScript
-
CSS的处理:
- CSS文件模块的加载、提取
- Less、Sass等预处理器的处理
-
资源文件img、font:
- 图片img文件的加载
- 字体font文件的加载
-
HTML资源的处理:
- 打包HTML资源文件;
-
处理vue项目的SFC文件.vue文件
-
1.2 使用前提
二、安装及简单使用
2.1 安装
-
webpack的安装目前分为两个:webpack、webpack-cli
-
两者之间的关系:
-
执行webpack命令,会执行node_modules下的.bin目录下的webpack;
-
webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;
-
而webpack-cli中代码执行时,才是真正利用webpack进行编译和打包的过程;
-
所以在安装webpack时,需要同时安装webpack-cli(第三方的脚手架事实上是没有使用webpack-cli的,而是类似于自己的vue-service-cli的东西)
-
-
安装命令
npm install webpack webpack-cli –g # 全局安装 npm install webpack webpack-cli –D # 局部安装 -
创建局部的webpack
-
创建package.json文件,用于管理项目的信息、库依赖等:
npm init -
安装局部的webpack:
npm install webpack webpack-cli –D -
使用局部的webpack:
npx webpack -
在package.json中创建scripts脚本,执行脚本打包即可:
npm run build
-
2.2 默认打包
-
在目录下直接执行
webpack命令 可以对项目代码进行打包 -
生成一个dist文件夹,里面存放一个main.js的文件,就是打包之后的文件;
-
webpack是如何确定打包的入口?
-
事实上,当运行webpack时,webpack会查找当前目录下的 src/index.js作为入口
-
所以,如果当前项目中没有存在src/index.js文件,打包会报错
-
-
当然,可以通过配置来指定入口和出口:
npx webpack --entry ./src/main.js --output-path ./build
2.3 配置文件
- 在根目录下创建一个webpack.config.js文件,来作为webpack的配置文件
const path = require('path') module.exports = { entry: './src/main.js', // 指定入口文件 output: { filename: 'bundle.js', // 出口文件名称 path: path.resolve(__dirname, './build') // 出口文件路径 } } - 配置文件一般为根目录下的 webpack.config.js,若修改名称需要在打包时指定配置
npx webpack --entry ./src/main.js --output-path ./build- 可将上述命令配置在package.json的脚本中
2.4 Webpack的依赖图
-
webpack是如何对项目进行打包
-
上webpack在处理应用程序时,它会根据命令或者配置文件找到入口文件
-
从入口开始,会生成一个 依赖关系图,这个依赖关系图会包含应用程序中所需的所有模块(比如.js文件、css文件、图片、字体等)
-
然后遍历图结构,打包一个个模块(根据文件的不同使用不同的loader来解析)
-
三、webpack打包css
3.1 loader的认识
webpack默认只能理解JavaScript和JSON文件,但实际工作中各种需求层出不穷,文件类型也多种多样.比如.vue、.ts、图片、.css等,这就需要loader增强webpack处理文件的能力
-
loader 的作用
-
loader 可以用于对模块的源代码进行转换
-
可以将css文件也看成是一个模块,我们是通过import来加载这个模块的
-
在加载这个模块时,webpack其实并不知道如何对其进行加载,必须制定对应的loader来完成这个功能
-
也就是说loader能够帮助webpack正确识别并处理它处理不了的文件
-
-
loader配置方式
-
rules属性对应的值是一个数组:[Rule]
-
数组中存放的是一个个的Rule,Rule是一个对象,对象中可以设置多个属性:
-
test属性:用于对 resource(资源)进行匹配的,通常会设置成正则表达式;
-
use属性:对应的值时一个数组:[UseEntry]
-
UseEntry是一个对象,可以通过对象的属性来设置一些其他属性
-
loader:必须有一个 loader属性,对应的值是一个字符串;
-
options:可选的属性,值是一个字符串或者对象,值会被传入到loader中;
-
query:目前已经使用options来替代;
-
-
传递字符串(如:use: [ 'style-loader' ])是 loader 属性的简写方式(如:use: [ { loader: 'style-loader'} ]);
-
-
loader属性: Rule.use: [ { loader } ] 的简写。
module: { rules: [ { // 匹配的文件 test: /\.css$/, // use: [ // // use中多个loader的使用顺序是从后往前 // { loader: "style-loader" }, // { loader: "css-loader" }, // ], // 简写一: 如果loader只有一个 // loader: "css-loader" // 简写二: 多个loader不需要其他属性时, 可以直接写loader字符串形式 use: [ "style-loader", "css-loader", "postcss-loader", // { // loader: "postcss-loader", // options: { // postcssOptions: { // plugins: ["autoprefixer"] // } // } // }, ], }, { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader", "postcss-loader"], }, ], }, -
3.2 css-loader
- 为css-loader只是负责将.css文件进行解析,并不会将解析之后的css插入到页面中
- 安装css-loader:
npm install css-loader -D
3.3 style-loader
-
如果要完成插入style的操作,那么还需要另外一个loader,就是style-loader
-
安装style-loader:
npm install style-loader -D
3.4 less-loader
- 使用less-loader,自动使less工具转换less到css
- less-loader会依赖less,所以安装时需要有less
npm install less -Dnpm install less-loader -D
3.5 postcss-loader
-
PostCSS是一个通过JavaScript来转换样式的工具
-
这个工具可以帮助进行一些CSS的转换和适配,比如自动添加浏览器前缀、css样式的重置
-
实现这些功能,需要借助于PostCSS对应的插件;
-
-
如何使用PostCSS呢?主要就是两个步骤:
-
查找PostCSS在构建工具中的扩展,比如webpack中的postcss-loader
-
选择可以添加需要的PostCSS相关的插件
- postcss-preset-env是一个postcss的插件:将一些现代的CSS特性,转成大多数浏览器认识的CSS,并且会根据目标浏览器或者运行时环境添加所需的polyfill;也包括会自动帮助我们添加autoprefixer(所以相当于已经内置了autoprefixer)
- 安装:
npm install postcss-preset-env -D
-
-
postcss-loader 安装
npm install postcss-loader -D- postcss需要有对应的插件才会起效果,所以需要配置它的plugin,可以在单独的配置文件 postcss.config.js
module.exports = { plugins: [ "postcss-preset-env" ] }
四、webpack打包其他资源
4.1 asset module type
-
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
-
asset/resource发送一个单独的文件并导出 URL:之前通过使用 file-loader 实现; -
asset/inline导出一个资源的 data URI:之前通过使用 url-loader 实现; -
asset/source导出资源的源代码:之前通过使用 raw-loader 实现; -
asset在导出一个 data URI 和发送一个单独的文件之间自动选择:之前通过使用 url-loader,并且配置资源体积限制实现
-
- asset/resource
- 打包两张图片, 并且这两张图片有自己的地址, 将地址设置到img或者bgi中
- 缺点: 多图片加载多次网络请求
- asset/inline
- 将图片进行base64的编码, 并且直接编码后的源码放到打包的js文件中
- 缺点: 造成js文件非常大, 下载js文件本身消耗时间非常长, 造成js代码的解析/执行时间过长
- asset
- 对于小一点的图片, 可以进行base64编码
- 对于大一点的图片, 单独的图片打包, 形成url地址, 单独的请求这个url图片
- module中的rule配置
{ test: /\.(png|jpe?g|svg|gif)$/i, // type: "asset/resource", // type: "asset/inline", // 3.合理的规范: // 3.1对于小图片,进行base64 // 3.2对于大图片,单独打包,形成url地址,单独请求 type: "asset", parser: { // 限制大小 dataUrlCondition: { maxSize: 28 * 1024 } }, generator: { // 占位符 // name: 原来的图片名称 // ext: 扩展名 // hash: webpack生成的hash filename: "img/[name]_[hash:8][ext]" } }, // 打包字体 { test: /\.(eot|ttf|woff2?)$/, type: "asset/resource", // 字体一般不做base64编码 generator: { filename: "font/[name]_[hash:8][ext]", }, },
4.2 打包js文件 - babel
-
Babel到底是什么呢?
- Babel是一个工具链,主要用于旧浏览器或者环境中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript
- 包括:语法转换、源代码转换等
-
Babel命令行使用
-
babel本身可以作为一个独立的工具(和postcss一样),不和webpack等构建工具配置来单独使用。
-
如果希望在命令行尝试使用babel,需要安装如下库:
-
@babel/core:babel的核心代码,必须安装
-
@babel/cli:可以让我们在命令行使用babel
-
npm install @babel/cli @babel/core -D
-
-
使用babel来处理源代码:
-
src:是源文件的目录
-
--out-dir:指定要输出的文件夹dist
-
npx babel src --out-dir dist
-
-
-
插件的使用
- 要转换箭头函数,那么我们就可以使用箭头函数转换相关的插件:
npm install @babel/plugin-transform-arrow-functions -D npx babel src --out-dir dist --plugins=@babel/plugin-transform-arrow-functions- 同时需要使用 plugin-transform-block-scoping 来完成const 转换成 var
npm install @babel/plugin-transform-block-scoping -D npx babel src --out-dir dist --plugins=@babel/plugin-transform-block-scoping,@babel/plugin-transform-arrow-functions -
Babel的预设preset
- 如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset):
-
安装@babel/preset-env预设:
npm install @babel/preset-env -D
-
单独配置 babel.config.js
module.exports = { presets: ["@babel/preset-env"], };
-
- 如果要转换的内容过多,一个个设置是比较麻烦的,我们可以使用预设(preset):
-
安装babel-loader:
npm install babel-loader -D
4.3 打包vue文件
- 安装vue:
npm install vue
-
vue-loader
npm install vue-loader -D
-
在webpack配置中设置Plugin
const { VueLoaderPlugin } = require("vue-loader") module.exports = { module: { rules: [ { test: /\.vue$/, loader: "vue-loader", }, ] }, plugins: [ new VueLoaderPlugin() ] }
4.4 resolve配置
-
webpack能解析三种文件路径:
-
绝对路径
- 由于已经获得文件的绝对路径,因此不需要再做进一步解析。
-
相对路径
-
在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录
-
在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径
-
-
模块路径
-
在 resolve.modules中指定的所有目录检索模块
-
默认值是 ['node_modules'],所以默认会从node_modules中查找文件
-
可以通过设置别名的方式来替换初识模块路径
-
-
-
确定文件还是文件夹
-
如果是一个文件:
-
如果文件具有扩展名,则直接打包文件
-
否则,将使用 resolve.extensions选项作为文件扩展名解析
-
-
如果是一个文件夹:
-
会在文件夹中根据 resolve.mainFiles配置选项中指定的文件顺序查找;
-
resolve.mainFiles的默认值是 ['index']
-
再根据 resolve.extensions来解析扩展名
-
-
-
-
extensions:解析到文件时自动添加扩展名:
-
默认值是 ['.wasm', '.mjs', '.js', '.json']
-
如果代码中想要添加加载 .vue 或者 jsx 或者 ts 等文件时,必须自己写上扩展名
-
-
alias:给文件路径起别名
- 当项目的目录结构比较深的时候,或者一个文件的路径可能需要 ../../../这种路径片段,可以给某些常见的路径起一个别名
module.exports = { resolve: { extensions: [".js", ".json", ".jsx", ".vue", ".ts", ".tsx"], alias: { utils: path.resolve(__dirname, "./src/utils") } }, }
五、Webpack的插件
5.1 认识Plugin
While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset management and injection of environment variables
-
Loader是用于特定的模块类型进行转换
-
Plugin可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等
5.2 CleanWebpackPlugin
- 帮助删除已存在的打包文件夹
- 安装和配置
npm install clean-webpack-plugin -D- 配置:
const { CleanWebpackPlugin } = require("clean-webpack-plugin") module.exports = { output: { filename: "bundle.js", path: path.resolve(__dirname, "./build"), clean: true // assetModuleFilename: "abc.png" }, plugins: [ new CleanWebpackPlugin() ] }
5.3 HtmlWebpackPlugin
-
对HTML进行打包处理可以使用另外一个插件:HtmlWebpackPlugin
-
npm install html-webpack-plugin -D -
使用指定模板
const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { plugins: [ new HtmlWebpackPlugin({ title: "webpack basic", template: "./index.html" }), ] }
5.4 DefinePlugin
-
DefinePlugin允许在编译时创建配置的全局常量,是一个webpack内置的插件(不需要单独安装)
-
例如:在
vue的模板中<link rel="icon" href="<%= BASE_URL %>favicon.ico">- 如果没有设置过这个常量值,就会会出现没有定义的错误
-
使用
plugins: [ new VueLoaderPlugin(), // new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: "电商项目", template: "./index.html" }), new DefinePlugin({ // 这里 "" 中的内容会被当成 js 代码来运行,用''包裹才是字符串 BASE_URL: "'./'", author: "'coder'", counter: "999" }) ] -
main.js 可以使用
// 使用通过DefinePlugin注入的变量 console.log(author) // coder console.log(counter) // 999 console.log(process.env.NODE_ENV)
5.5 CopyWebpackPlugin
-
作用:在webpack打包完成后会把静态资源复制到打包的文件夹下
npm install copy-webpack-plugin@9 -D- 在部署时,通过此插件将静态资源进行复制到打包后的资源中
-
配置如下:
const CopyWebpackPlugin = require("copy-webpack-plugin") module.exports = { plugins: [ new CopyWebpackPlugin({ patterns: [ { // 从哪个文件 from: "public", // 到哪个文件,当前打包的文件夹下 to: "./", globOptions: { // 忽略的文件 ignore: [ "**/index.html" ] } } ] }) ] }
5.6 mode配置
-
Mode配置选项,可以告知webpack使用相应模式的内置优化:
- 默认值是production(什么都不设置的情况下)
- 可选值有:'none' | 'development' | 'production'
-
区别
六、webpack-dev-server
6.1 使用webpack-dev-server
-
安装webpack-dev-server:
npm install webpack-dev-server -D -
修改配置文件,启动时加上serve参数:
"serve": "webpack serve" -
webpack-dev-server 在编译之后不会写入到任何输出文件,而是将 bundle 文件保留在内存中:
- 事实上webpack-dev-server使用了一个库叫memfs(memory-fs webpack自己写的)
6.2 HMR热模块替换
-
什么是HMR呢?
-
HMR的全称是Hot Module Replacement,翻译为模块热替换
-
模块热替换是指在 应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面
-
-
HMR通过如下几种方式,来提高开发的速度:
-
不重新加载整个页面,这样可以保留某些应用程序的状态不丢失
-
只更新需要变化的内容,节省开发的时间
-
修改了css、js源代码,会立即在浏览器更新,相当于直接在浏览器的devtools中直接修改样式
-
-
如何使用HMR呢?
-
默认情况下,webpack-dev-server已经支持HMR,只需要开启即可(默认已经开启)
-
在不开启HMR的情况下,当修改了源代码之后,整个页面会自动刷新,使用的是live reloading
if (module.hot) { module.hot.accept("./utils/demo.js", () => { console.log("demo发生了更新"); }); } -
6.3 devServer配置
-
- 该配置项允许配置从目录提供静态文件的选项(默认是
public文件夹) - devServer将所有静态资源进行打包放入内存(memfs)中,开启express服务器,访问的时候会下载
index.html(会依赖静态资源),此处static可以指定一个文件夹,浏览器请求资源从打包的资源请求不到的时候会让express服务器从该文件夹中将静态资源读到内存中供浏览器访问 - 注意: 只有在开发阶段希望提供静态文件时才需要这样做,告诉服务器从哪里提供内容;打包阶段通过
CopyWebpackPlugin进行复制到打包的文件夹下- 比如:单独引入 assets 下的某张图片时,需要配置
const path = require('path'); module.exports = { //... devServer: { static: { directory: path.join(__dirname, 'public'), }, }, }; - 该配置项允许配置从目录提供静态文件的选项(默认是
-
host
- 默认值是localhost;
- 如果希望其他地方也可以访问,可以设置为 0.0.0.0;
-
port
- 设置监听的端口,默认情况下是8080
-
open
- 是否打开浏览器:默认值是false,设置为true会打开浏览器
-
compress
- 是否为静态文件开启gzip compression:默认值是false,可以设置为true
-
proxy
-
target:表示的是代理到的目标地址,比如请求到
/api/users在会被代理到请求http://localhost:3000/api/users
-
pathRewrite:默认情况下,
/api也会被写入到URL中,如果希望删除,可以使用pathRewrite -
secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false
-
changeOrigin:表示是否更新代理后请求的headers中host地址
-
- historyApiFallback
- 当使用 HTML5 History API 时,任意的
404响应都可能需要被替代为index.html。 - 将该属性设为true即可
const path = require("path") module.exports = { devServer: { static: path.resolve(__dirname, "./public"), hot: true, // 开启HMR host: "0.0.0.0", port: 8888, open: true, // 自动打开浏览器 // compress: true, proxy: { "/api": { target: "http://localhost:3000", pathRewrite: { "^/api": "" }, secure: false, changeOrigin: true } } }, } - 当使用 HTML5 History API 时,任意的
七、区分环境
- webpack.com.conf.js
const path = require("path");
const { VueLoaderPlugin } = require("vue-loader/dist/index");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { DefinePlugin } = require("webpack");
module.exports = {
entry: "./src/main.js",
output: {
filename: "js/bundle.js",
path: path.resolve(__dirname, "../build"),
},
resolve: {
extensions: [".js", ".json", ".vue", ".jsx", ".ts", ".tsx"],
alias: {
utils: path.resolve(__dirname, "../src/utils"),
},
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader", "postcss-loader"],
},
{
test: /\.(png|jpe?g|svg|gif)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 28 * 1024,
},
},
generator: {
filename: "img/[name]_[hash:8][ext]",
},
},
{
test: /\.(eot|ttf|woff2?)$/,
type: "asset/resource",
generator: {
filename: "font/[name]_[hash:8][ext]",
},
},
{
test: /\.js$/,
use: [
{
loader: "babel-loader",
},
],
},
{
test: /\.vue$/,
loader: "vue-loader",
},
],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
title: "电商项目",
template: "./public/index.html",
}),
new DefinePlugin({
BASE_URL: "'./'",
// 对vue2做适配,是否支持optionsApi
__VUE_OPTIONS_API__: true,
// 生产环境不需要支持调试
__VUE_PROD_DEVTOOLS__: false,
author: "'coder'",
counter: "999",
}),
],
};
- webpack.dev.conf.js
const path = require("path")
const { merge } = require("webpack-merge")
const commonConfig = require("./webpack.com.config")
module.exports = merge(commonConfig, {
mode: "development",
// 设置source-map, 建立js映射文件, 方便调试代码和错误
devtool: "source-map",
devServer: {
static: path.resolve(__dirname, "../public"),
hot: true,
host: "0.0.0.0",
port: 8888,
// open: true,
proxy: {
api: {
target: "http://localhost:7777",
pathRewrite: {
"^/api": "",
},
// secure: false,
changeOrigin: true
},
},
},
});
- webpack.prod.conf.js
const { merge } = require("webpack-merge");
const CopyWebpackPlugin = require("copy-webpack-plugin")
const commonConfig = require("./webpack.com.config");
module.exports = merge(commonConfig, {
mode: "production",
output: {
// 在生成文件之前清空 output 目录
clean: true,
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{
// 从哪个文件
from: "public",
// 到哪个文件
to: "./",
globOptions: {
// 忽略的文件
ignore: [
"**/index.html"
]
}
}
]
})
],
});
- 安装 merge:
npm install webpack-merge -D
const { merge } = require("webpack-merge");
const commonConfig = require("./webpack.com.config");
module.exports = merge(commonConfig, {
mode: "production",
output: {
clean: true,
},
});