本文档是我在b站看到的一套学习视频,我觉着这套视频讲的很棒,就认真的跟着视频一个案例一个案例的敲下来,并用自己总结写出自己的文档,这样可以更印象深刻,初学者建议直接视频链接跳走,自己跟着敲一遍!
1.0 为什么使用webpack?
传统前端html页面通过script标签引入js文件会带来很多问题,比如js文件太多代码可维护性变差,js文件太大会影响页面渲染,一些库会在window上绑定对象,比如jq.会有全局污染等等一系列问题,那这些问题怎么解决?早些年的项目有GRUNT和GULP,这两个工具是任务执行器,他们是将所有的项目文件拼接在一起,利用的是立即调用函数表达式,来解决了作用域问题,refine.js来解决代码模块化的问题,这些就不详细介绍了,来介绍主角webpack。
1.1 自定义webpack.config.js
安装nodejs,webpack以及webpack-cli不再赘述,从webpack的入口文件讲起,webpack.config.js需要建立在文件根目录下,由于webpack是在nodejs中运行的,所以定义模块的时候要使用nodejs的commonjs去定义,先建一个index.html,引入一个src下的index.js,index.js中只做一件事引入平级目录下的main.js
-- index.js --
import hello from "./main"
hello();
-- main.js --
function hello(){
console.log('hello');
}
export default hello;
-- webpack.config.js --
const path = require('path');
module.exports = {
//入口
entry:"./src/index.js",
//出口
output:{
filename:"bundle.js",
clean: true, //每次打包删除之前的文件
path: path.resolve(__dirname,'./dist')
},
mode: "none",
}
现在我们就可以执行一下webpack执行,去打包一下这一个方法了,执行完毕以后可以看到dist/bundle.js已经打包出来了,可以正常引入执行方法。小试牛刀已经可以了,接下来的demo都是在此基础上增加配置,继续学习!
1.2 插件的使用
1.1中我们只是打包了一个js文件,还需要手动引入打包后的文件才能看到效果,接下来我们使用插件功能来自动帮我们操作这些,这里用到HtmlWebpackPlugin,首先npm安装这个插件,然后引入并添加到config中
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...,//重复的地方我就省去了
plugins:[
new HtmlWebpackPlugin({
template :"./index.html", //模板文件
filename :"app.html", //新模板文件名称
inject: "body" //添加到的节点位置
})
],
}
1.3 环境
我们可以通过webpack.config.js中的mode设置打包的模式,当我们把mode值设置成development的时候,我们会发现此时bundle.js中打包的js十分不便于我们观看,我们可以使用source-map,增加配置devtool设置,同时我们可以按照webpack-dev-server帮助我们启动本地环境,并且监听文件变化自动刷新页面
module.exports = {
...,//重复的地方我就省去了
mode: "development",
devtool: "inline-source-map",
devSrever: {
static: "./dist"
}
}
1.4 资源模块
以上我们学习到的内容只能是去帮助我们打包js,那么其他的资源如何引入呢?我有做过一个layui的项目,在layui-admin的这个框架中,我配置的gulpfile.js中对于图片的处理是直接移动文件到指定的文件夹下,接下来我们看一下webpack是如何处理的。
webpack使用内置资源模块(asset modules)来引入其他任何类型的资源,一共有四种资源类型模块
- asset/resource 发送一个单独的文件导出url
- asset/inline 导出一个资源的data url
- asset/source 会导出资源的源代码
- asset 会在resource和inline中自动选择
资源模块的使用:
module.exports = {
...,
module : {
rules: [
{
test : /\.png$/,
type: 'asset/resource',
generator:{
filename: "images/[contenthash][ext]"
}
},
{
test : /\.svg$/,
type: 'asset/inline'
},
{
test : /\.txt$/,
type: 'asset/source'
},
{
test : /\.jpg$/,
type: 'asset',
parser: {
dataUrlCondition : {
maxSize: 4 * 1024 *1024
}
}
}
]
}
}
1.5 关于loader
webpack除了资源模块以外,还可以通过loader来引入外部资源,loader可以让webpack可以理解除js,json以外的其他类型文件,loader定义有两个重要的属性,一个是test判断类型格式,一个是use,指定我们使用什么loader。
以css-loader为例我们使用一下,首先npm安装css-loader和style-loader,配置一下rules
module.exports = {
...,
module : {
rules: [
{
test : /\.css$/,
/**
先要写style-loader后写css-loader,这里是因为要先使用css-loader
打包识别css文件,然后通过style-loader把样式放置到页面上
*/
use: [
'style-loader',
'css-loader'
],
}
]
}
}
再加入less-loader和less
module.exports = {
...,
module : {
rules: [
{
test : /\.(css|less)$/,
use: [
'style-loader',
'css-loader',
'less-loader'
],
}
]
}
}
mini-css-extract-plugin插件抽离css文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...,
plugins:[
new MiniCssExtractPlugin({
filename: "styles/[contenthash].css"
})
],
module : {
rules: [
{
test : /\.(css|less)$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
],
}
]
},
}
压缩css文件,安装css-minimizer-webpack-plugin,并把mode改为production,增加到optimization优化配置中
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
...,
mode: "production",
optimization: {
minimizer: [
new CssMinimizerWebpackPlugin()
]
}
}
1.6 babel-loader
安装babel的三个包 babel-loader @babel/core @babel/preset-env 编译async await语法时会报错 还需要再安装 @babel/runtime 和 @babel/plugin-transform-runtime
module.exports = {
...,
module : {
rules: [
{
test : /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [ '@babel/preset-env' ],
plugins:[
[
'@babel/plugin-transform/runtime'
]
]
}
}
},
]
},
}
1.7 代码分离
常见的代码分离的方法
- 使用entry手动配置入口节点
module.exports = {
...,
entry:{
index: "./src/index.js",
another: "./src/another.js",
},
output:{
filename:'[name].bundle.js',
path: path.resolve(__dirname,'./dist'),
clean: true,
assetModuleFilename: "images/[contenthash][ext]"
}
}
以上代码配置了两个入口,index.js和another.js都引入了lodash库,那么lodash会打包到各自的包中
- 防止重复的分离方法,在入口的地方通过entry dependencies 或 splitChunksPlugin 去重和分离代码
// dependencies
module.exports = {
...,
entry:{
index: {
import: "./src/index.js",
dependOn: "shared"
},
another: {
import: "./src/another.js",
dependOn: "shared"
},
shared: "lodash"
},
}
//splitChunksPlugin
module.exports = {
...,
entry:{
index: "./src/index.js",
another: "./src/another.js"
},
optimization: {
splitChunks: {
chunks: "all"
}
}
}
- 动态导入:通过模块的内联函数调用来分离代码
function getCompoment(){
return import('lodash').then(({ default: _ })=>{
const element = document.createElement('div');
element.innerHTML = _.join(['1','2','3'],'*');
return element
})
}
getCompoment().then((element)=>{
document.body.appendChild(element);
})
动态导入的应用:懒加载:点击按钮的时候引入方法,不点击就不加载
const button = document.createElement('button');
button.textContent = "点击+++"
button.addEventListener('click',()=>{
import('./math.js').then(({ add })=>{
console.log(add(3,4));
})
})
document.body.appendChild(button);
预获取webpackPrefetch和预加载webpackPreload,webpackPrefetch会在我们点击按钮的时候加载js,并在头部创建一个标签prefetch加载js
<link rel="prefetch" as="script" href="http://localhost:8080/src_math_js.bundle.js">
import(/* webpackPrefetch: true */'./math.js')
1.8 缓存
我们通过output.filename设置contenthash,每当文件内容发生变化的时候输入contenthash的文件名,来使浏览器重新请求文件,解决缓存的问题,但像是第三方的库,一般很少修改,我们可以把他们提取到单独的vendor chunk文件中,利用浏览器的长效缓存机制,命中缓存来消除请求,减少向server获取资源。
optimization: {
minimizer: [
new CssMinimizerWebpackPlugin()
],
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
1.9 环境变量
我们可以在执行打包命令时传入参数控制环境
// npx webpack --env production
module.exports = (env) => {
return {
mode: env.production ? "production" : "development"
}
}
拆分配置文件,新建config文件夹里面新建webpack.config.dev.js和.prod.js,指定script脚本运行时的配置文件
"scripts": {
"start": "webpack serve -c ./config/webpack.config.dev.js",
"build": "webpack -c ./config/webpack.config.prod.js"
},
提取公共配置合并配置文件:新建webpack.config.common.js里面保留公共的部分,并删除dev和prod中公共的部分,安装webpack-marge
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.config.common');
const productionConfig = require('./webpack.config.prod');
const developmentConfig = require('./webpack.config.dev');
module.exports = (env) => {
switch(true) {
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
defult:
return new Error('error')
}
}
"scripts": {
"start": "webpack serve -c ./config/webpack.config.js --env development",
"build": "webpack -c ./config/webpack.config.js --env production"
},
2.0 souce-map
SourceMap是一种映射关系。当项目运行后,如果出现错误,错误信息只能定位到打包后文件中错误的位置。如果想查看在源文件中错误的位置,则需要使用映射关系,找到对应的位置。
推荐方式开发环境使用cheap-module-eval-source-map 生产环境不开启souce-map
2.1 devSrever
开发环境中,为我们启动一个web服务器,模拟用户从浏览器读取我们的web服务,首先npm安装webpack-dev-server
devServer: {
static : "./dist",
compress: true, //设置content-Encoding:gzip压缩代码 提到效率
port: 3000, //配置端口号
headers : { //设置Response Headers
'X-Access-Token': 'abcd123'
},
proxy:{
'/api': 'http://localhost:9000'
},
https: true,
historyApiFallback: true, //解决spa项目history模式刷新报错问题
hot: true, //模块热替换:会在应用程序运行中替换添加或删除模块,而无需加载整个页面
}
2.2 模块解析
//相对路径
const math = require('./math2.js');
//绝对路径
import getHeader from "/src/components/header.js"
import body from "/src/components/a/b/body.js"
//模块路径
import _ from 'lodash';
console.log(math.add(5,4));
console.log(getHeader());
console.log(_.join(['1','2','3'],'*-*'));
body();
//components/a/b/body.js
const math = require('@/math2.js');
resolve : {
//别名
alias: {
'@': path.resolve(__dirname, '../src')
},
//引入文件格式优先级设置
extensions: [ '.json', '.js', '.vue' ]
}
2.3 外部扩展
引入第三方库,不希望打包到项目中,比如jq
externals: {
jquery : 'jQuery'
}
2.4 web works
//work.js
self.onmessage = (message) => {
self.postMessage({
answer: 1111
})
}
const worker = new Worker(new URL('./work.js', import.meta.url));
worker.postMessage({
question: "请求问题"
})
worker.onmessage = (message) => {
console.log(message.data.answer);
}