webpack作为现代前端开发最火的模块打包工具,已经成为了前端工程师必备的技能之一。
是:
前端资源构建工具;
静态模块打包器;
webpack从入口文件开始,根据模块的依赖关系进行分析,然后生成加工后的静态资源;
将高级语法转换成兼容性高的通用语法;
引入chunk块概念–打包–>bundles;
基于nodejs平台的工具,遵循commonjs模块化规范
本章重点讲解:
预备技能
环境参数
npm i -D webpack webpack-cli
webpack-cli是做什么的?
如果你使用 webpack v4+ 版本,并且想要在命令行中调用 webpack,你还需要安装 CLI。
通过查看webpack脚本,得出webpack命令,本质就是调用了webpack-cli来实现的。webpack4+将这个功能单独拆解出来了。
const runCli = cli => {
const path = require("path");
const pkgPath = require.resolve(`${
cli.package}/package.json`);
// eslint-disable-next-line node/no-missing-require
const pkg = require(pkgPath);
// eslint-disable-next-line node/no-missing-require
require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
};
/** @type {CliOption} */
const cli = {
name: "webpack-cli",
package: "webpack-cli",
binName: "webpack-cli",
installed: isInstalled("webpack-cli"),
url: "https://github.com/webpack/webpack-cli"
};
if (!cli.installed) {
const path = require("path");
const fs = require("graceful-fs");
const readLine = require("readline");
const notify =
"CLI for webpack must be installed.\n" + ` ${
cli.name} (${
cli.url})\n`;
console.error(notify);
// ...
} else {
runCli(cli);
}
在cli中执行入口文件。
# 因为没有全局安装webpack,所以需要制定本地webpack
./ node_modules/.bin/webpack ./src/index.js
# 如果global安装了
webpack ./src/index.js
通过npm脚本执行-package.json:
scripts: {
// npm 会主动去查找环境中的webpack
"webpack": "webpack ./src/index.js"
}
上面全部使用webpack的默认配置。
在目录下新增:webapck.config.js,webpack默认会在项目根目录下查找这个文件,当作自定义配置项。
module.exports = {
mode: 'development'
}
如果放在其他的地址,需要在脚本中显式指定。如webpack/dev.js
scripts: {
"webpack": "webpack --config ./webpack/dev.js ./src/index.js"
}
其他的配置项,可以放到配置文件,也可以当作脚本参数传入。【推荐:复杂的放到配置文件】
原始代码:
// demo1.js
const greeting = 'Hello World!';
console.log(greeting);
// index.js
import './demo1'
执行脚本webpack后:
npm run webpack
默认情况下,输出文件为:dist/main.js
node dist/main.js
# 成功输出
# Hello World!
结论:打包js成功了!
webpack默认支持js/json文件,可以省略文件后缀。
如果想要打包css文件呢?
// style1.less
body {
color: red}
// index.js
import './style'
报错如下:
[no extension] src/style1 doesn't exist
[.js] src/style1.js doesn't exist
[.json] src/style1.json doesn't exist
[.wasm] src/style1.wasm doesn't exist
[as directory] src/style1 doesn't exist
用所有默认支持的格式去匹配,都找不到该文件。
然后,指明文件后缀:
import './style1/css'
报错如下:
Module parse failed: Unexpected token.
You may need an appropriate loader to handle this file type...
结论:需要安装loader
需要借助loader,配置webpack.config.js
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
当webpack匹配到css结尾的模块时:
使用html-webpack-plugin插件:
npm i -D html-webpack-plugin
使用:
const htmlWebpackPlugin = require('html-webpack-plugin')
// plugin: 下载 引入 使用
plugins: [
// html-webpack-plugin
new htmlWebpackPlugin()
]
执行:npm run webpack
结果:在output指定目录下,生成了一个index.html文件。并且将bundle文件–main.js插入到了script标签中,默认将script放在head中。
如果在css中引入了图片资源:
background-image: url(./images/image.png);
那么需要用到url-loader(用来处理css文件中url相关图片文件的)和file-loader【看情况安装】:
{
test: /\.png$/,
loader: 'url-loader',
options: {
// 默认情况limit没有限制;下面限制为:当图片小于1kb,才会用url-loader进行处理,将图片转化为base64编码字符串【优点:可以减少http请求;缺点:加大js文件体积】
// 当图片大雨1kb时,url-loader就需要依赖file-loader包,将图片直接copy到dist目录下,默认资源名字会变成hash名字
limit: 1 * 1024
}
}
如果在html中引入image标签,上面的处理就会出问题了,因为,例如file-loader会修改图片的名字,此时,html会找不到图片资源,我们需要用到html-loader。
{
test: /\.html$/,
// 处理html中的img标签资源,负责将img资源引入,然后才能被url-loader处理。
// html-loader引入图片是es6规范的,解析时会报错
// 解决办法:将html-loader的esModule设置为false
loader: 'html-loader',
options: {
esModule: false
}
}
不需要做压缩等处理,只需要,复制并输出。
比如字体文件:iconfont.ttf iconfont.svg
方案:用排除法匹配其他资源,然后用file-loader处理。匹配到的资源,将会用hash重命名后,输出到dist目录
rules: [
{
exclude: /\.(html|css|js)/,
loader: 'file-loader'
}
]
开发服务器:用来自动构建、打开浏览器,并自动刷新等功能。大大提升了开发效率。
// 开发服务器只在内存中编译打包,没有任何输出
// 用npx webpack serve
devServer: {
contentBase: './dist',
// 启动gzip
compress: true,
port: 3000
}
需要安装webpack-dev-server。
npm i -D webpack-dev-server
当你改动源代码的时候,webpack就会自动打包,并更新页面
一般情况下,webpack已经针对不同环境进行了默认的配置配置,只需要用mode去开启就行了。
所以脚本上,我们需要传入不同的参数,或者是建立不同mode的相关config文件。
// 如果只有一个webpack.config.js文件,那么我们可以通过给cli传入参数
"dist": "webpack --env=dist"
"dev": "webpack --env=dev"
// 使用
const {
env} = require('minimist')(process.argv.slice(2), {
string: ['env'],
});
// 然后根据env给webpack的配置项传入不同的值。
// 或者可以将配置文件指定为:dev.config.js/dist.config.js等,然后在脚本中使用不同的文件
"dist": "webpack --config=webpack/dist.config.js"
"dev": "webpack --config=webpack/dev.config.js"
常用的语法检查工具是eslint。我们可将eslint嵌到webpack中,用eslint-loader【以来esling】
rules: [
{
// 注意:只检查源代码,需要排除node_modules中的代码
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
// 手动配置不同的规则,或者使用第三方的规则,如:airbnb
// npm i -D eslint-config-airbnb-base eslint-config-import
// 将airbnb配置到package.json中:eslintConfig: {extends: 'airbnb-base'}
options: {
// 自动修复
fix: false
}
}
]
然后执行webpack,如果js代码中有些语法不符合airbnb的规范,那么webpack操作就会失败,并输出:错误
如果将fix设置为true,那么错误会自动处理,webpack大部分情况不会被打断,除非eslint处理不了。
正常情况下,源代码如果使用es6及以上语法,webpack并不会进行转换,此时若将bundle直接放到低版本浏览器(IE)中运行,脚本会报错,如:不认识const等等。
那么我们需要用到babel:
npm i -D babel-loader @babel/core @babel/preset-env @babel/polyfill core-js
rules: [
{
test: /\.js$/,
excludes: /node_modules/,
loader: 'babel-loader',
options: {
// 预设:指示babel做什么样的兼容性处理,preset-env就是基本处理,将语法转化为es5及以下兼容性强的语法【基本语法】
// presets: '@babel/preset-env'
// 如果用到promise等语法,那上面的preset-env并不能处理,就需要其他的包:@babel/polyfill 在源代码入口引入就可以了。
// 问题:我只需要做一部分兼容性处理,但引入@babel/polyfill体积太大了。。。。那么就需要做按需加载---core-js。然后不需要引入:@babel/polyfill
presets: [
'@babel/preset-env',
{
// 按需加载
useBuiltIns: 'usage',
// 指定版本
corejs: {
version: 3},
// 指定兼容到哪个版本的浏览器
targets: {
chrome: '60', firefox: '60', ie: '9'}
}
]
}
}
]
所以最终方案为:@babel/preset-env + core-js
webpack开启:
mode: 'prodcution'
配置插件:
plugins: [
// html-webpack-plugin
new htmlWebpackPlugin({
template: './index.html',
title: 'Demo',
inject: 'body',
minify: {
// 如:移除空格等等
collapseWhitespace: true,
}
})
]
需要考虑哪些点呢?需要考虑如下两类:
1. 开发环境优化
// html不需要HMR
entry: ['./src/index.js', './index.html']
devtool: 'inline-source-map'
// index.js
if(module.hot) {
// 如果开启了HMR,则监听下面模块,如果变了,就只打包下面模块
module.hot.accept('./...js', function(){
})
}
2. 生产环境优化
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
// 开启多进程打包
'tread-loader',
{
loader: 'babel-loader', options: {
presets: []}}
]
}
]
optmization : {
splitChunk: {
// all的意思:将node_modules中代码打包成一个单独文件:vendor.js
// 多入口文件,会共用vendor.js
// 我们还可以将node_modules中包,拆分成单独的包,例如xlsx太大,可以单独引入
chunks: 'all'
}
}
plugins: [
new workboxWebpackPlugin.GenerateSW({
// serviceWorker快速启动
// 删除旧的serviceWorker
// 生成一个serviceWorker配置文件
clientClaim: true,
skipWaiting: true
})
]
// 入口文件 index.js中注册serviceWorker
// 1. eslint默认不认识navigator/window全局变量,需要改package.json中eslintConfig.env: {browser: true},表示支持浏览器的变量
// 2. ==SW的代码必须运行在服务器上==,而不能直接通过file://协议访问。
if('serviceWorker' in navigator) {
window.addEventListener('load', ()=> {
// service-worker.js这个文件就是上面webpack生成的配置文件
navigator.serviceworker.register('/service-worker.js')
.then((success) => {
console.log(success)
})
.catch((error) => {
console.log(error)
})
})
}
externals: {
// 库名: npm包名
jquery: 'jQuery'
}