webpack5.0基础配置(全面)

前言

铁子们好我是跑不快的猪,新的一年,新的开始,先预祝各位都有华丽丽的变身。

本篇文章主要进行webpack5.0+版本的配置,在这个脚手架横行的时代,最终还是需要掌握一些基础的配置,对工作、面试、以及各脚手架中webpack的调试都有不小的作用。接下来会一步步的分享我学习配置中的一些坑和心得。

最终是配置了常用vue+vuex+vue-router的单页面应用工程

文末可以直接下载demo,修修改改可以简单开发,会逐步更新!

请注意,在每个配置讲解中路径与项目架构中的路径一致

概念预热

在学习webpack的过程中,有如下几个概念贯穿始终,在开始之前先列出来。掌握了它们虽然不至于事半功倍,但是对一些配置的理解可能会更简单一点。

  • module(模块):预备被webpack处理的,相对整个程序的离散功能块,它们遵循commonJs规范、umd规范、ES6 Module,提供可靠的抽象和封装界限
  • chunk(块):在被webpack打包中,将模块按照引用关系合并的模块集合称之为chunk,webpack可以对这些chunk进行操作。
  • bundle:bundle是在chunk经过处理后,最终输出的也就是可以上线的文件。

安装

  • npm init 初始化package.json文件
  • yarn add webpack webpack-cli -D 使用喜欢的包模块管理工具安装webpack和webpack脚手架到工程目录下
  • 创建webpack.config.js文件
// webpack.config.js
module.exports = {
	// 关于webpack的配置写在这里
}

至此,准备工作已经完成了,这里要注意:运行webpack5的node版本最低要是10.13.0(LTS)

在mac上使用n模块,在windows使用nvm模块可以更快更方便的切换node版本

工程目录

关于工程目录没有什么可说的,构建一个清晰的,模块化的并且操作起来方便的工程目录即可,本篇中使用的工程目录如下
webpack5.0基础配置(全面)_第1张图片

  • build:放入webpack的一些配置文件
  • src-assets:放入静态资源比如图片、字体、音频、视频等
  • src-store:vuex配置文件夹
  • src-routes:vue路由配置文件夹
  • src-entry: 工程的一些入口文件,如:App.vue等
  • src-views:路由对应的每个vue插件

入口(entry)

开始正式进入正题,当我们需要使用webpack打包的开始,首先要告诉webpack从哪里开始进行,也就是入口。
此处还有个不属于入口配置,但是与入口息息相关的配置context,上下文配置。这个配置能干嘛呢,其实就是将配置中的相对路径改变了一下,默认我们的相对路径是针对当前的工程目录

// 会找src/index.js
module.exports = {
	entry: './src/index.js'
}

// 会找app1/src/index.js
module.exports = {
	context: 'app1',
	entry: './src/index.js'
}

context我能想到的用法,就是当项目非常庞大,由多个单独项目组成,但是打包功能相似,可以使用一套配置打包,官方给出的解释是这个功能让你的配置独立与CWD(当前工作目录)也就是你的配置可以放在任意地方。
说回entry,它支持多种类型string | [string] | {},不同类型配置作用于不同的场景之下。

  • string 配置一个入口文件,比如配置一个SPA项目
  • [string]多用于将多个文件打包成一个文件,进行一下融合
  • {} 对象语法的配置是扩展性最高的,可以对入口进行更细颗粒度的配置,推荐使用这个配置

由于入口对象的配置比较少所以这里列一下然后简单说明下

  • dependOn: 当前入口所依赖的入口
  • filename:输出的文件名称
  • import:启动时加载的模块
  • library:制定library选项,为当前entry创建一个library
  • runtime:运行时chunk的名字,设置了之后,会创建一个新的运行时
  • publickPath:当输出文件被引用时,制定一个公共的URL地址

library :这个东西要关注一下,我们不光可以使用webpack来进行应用工程的打包,也可以用webpack来进行库的创作,而这个libraray以及文档中很多关于它的相关配置,在创建一个库的时候便是极其有用的。

runtime:将运行时文件单独打包出来为一个chunk,将运行时文件和我们的项目代码进行拆分,运行时文件大意其实就是一个在辅助浏览器通过mainfest来进行解析、查找的功能块,具体这里不做分析,后期会专门做出一期来讲讲这个。

dependOn:这个属性主要是为了将入口文件中共用的模块进行一个提取,算是一个优化操作,提取出来的模块不常改动,可以利用浏览器的缓存机制进行缓存,来提高我们的页面打开速度。

// a.js
export const add = (a, b) => a + b;

// main.js
import {add} from './a.js';
console.log(add(3,4));

// main2.js
import { add } from './a.js';
console.log(add(1, 2));

如上面的例子,在main.jsmain2.js中全部引用了a.js, 这个时候我们就可以进行一个提取打包

entry: {
        index: {
            import: path.resolve(__dirname, '../src/index.js'),
            dependOn: 'vender'
        },
        main: {
            import: path.resolve(__dirname, '../src/entry/c.js'),
            dependOn: 'vender'
        },
        vender: path.resolve(__dirname, '../src/entry/vender.js')
    },

如此一来,我们会把两个文件中共用的文件进行额外打包。如下:
webpack5.0基础配置(全面)_第2张图片
最终的bundle有三个。并且这样做会减少“index.js”和“main.js”的体积大小。

webpack入口的默认值是’./src/index.js’。

出口(output)

出口定义了当我们通过webpack 对于 chunk 一系列的打包工作完成后,根据自己的需求定义一下该如何输出,关于“出口”的配置项有非常之多,从功能上分为:位置、名称、方式等。从目标文件上分为:静态资源、入口模块、单独提取出的公共chunk等。这里我们仅仅做一个简单配置。

// webpack.config.js
module.exports = {
	...
	output: {
		filename: '[name].[contenthash].js',			// 输出文件名
		path: resolvePath(__dirname, '../dist',			// 输出文件的目标文件夹
		clean: true,									// 每次打包后是否清除dist
		hashDigestLength: 5,							// hash值的长度
		assetModuleFilename: '[name][contenthash][ext] 	// 静态资源的文件名
	}
	...
}

webpack5.0版本新增了一个clean 是非常好用的,在之前的版本,我们每次打包工程的时候要通过 clean-webpack-plugin 来对目标文件夹的内容进行清除,现在通过这一个属性就可以做到,而且能够对其进行具体配置,通常情况下配置为 true 就可以。更详细的可以参考官方文档

hashDigestLength 则是对输出文件的那串hash值的长度进行了一个控制。

assetModuleFilename 是对一些静态资源文件名称配置了。

这里有个坑,如果你使用 webpack-dev-server启动了一个服务的时候,不要在出口中配置 clean ,否则会导致热更新生成的 json 文件被清除,无法达到热更新效果

mode

当你配置好入口和出口之后,进行一个简单的打包,会发现如下提示
在这里插入图片描述
它的意思就是,您好,您当前的配置没有指定mode,我们为您默认指定了 production , 请您为不同的环境设置进行mode的设定。

在一个项目从创建到上线,最少会有两个阶段。一个是“开发阶段”,我们在这里进行开发、调试、接口对接。还有一个就是“上线阶段”,也就是可以熬夜发版,然后坐等公司赚钱的阶段。这两个阶段对应两个环境就是“开发环境”和“生产环境”。

这里的 mode 就是我们去对应上述不同阶段(环境)的开关

  1. 在开发阶段,我们往往需要更明确的“source-map”文件,来定位错误,需要一个明确的本地环境模拟上线的环境。这些通过它都可以实现。

  2. 在“生产环境”我们则需要的是体积更小的文件,能去除无用代码的功能(tree shaking)

// webpack.config.js
module.exports = {
	...
	mode: 'development',  // 开发环境(production:生产环境)
	...
}

module

这个东西基本上是webpack配置的核心了,通过它我们能对不同类型的文件进行处理,并且最终来进行打包。大体的模版如下:

// webpack.config.js
module.exports = {
	...
	module: {
		rules: [
			{
				test: '',
				use: {
					loader: ''
				}
			}
		]
	}

虽然看起来大致模版如此简单,但是实际上在 module.rules 中能配置的东西如此让人眼花撩乱。

在webpack5中,它为我们提供了一个 Asset Modules ,这是一个静态资源模块用来处理我们的静态资源。能让我们处理的更加简单。

// webpack.config.js
module.exports = {
	module: {
		rules: [
			{
				test: /.(jpg|png|svg)$/,
				type: 'asset',
				parser: {
					dataUrlCondition: {
						maxSize: 10 *1024
					}
				},
				generator: {
					outputPath: 'images'
				}
			}
		]
	}
}

如上的代码其实就是配置了一个简单的处理图片资源的规则,它实现的是如果是10kb以下的图片资源采用base64的方式进行打包。否则则发送一个单独的文件导出。对外输出的文件夹是 images

相比webpack4我们不需要再去安装 url-loader file-loader 来进行对静态资源的处理。在webpack5中,通过 type 的配置可以将静态资源打包成我们所需要的大多数模式。所有的 type 类型有如下几种:

  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

另外啰嗦一下,对于 rules 中的 loader 处理方式是从后往前比如

// webpack.config.js
// 要先下载miniCssExtractPlugin插件
module.exports = {
	modules:{
		rules: [
			{
                test: /.scss$/,
                // 提取css为单独的文件,可以不使用style-loader
                use: [
                    isEnv() ? 'style-loader' : MiniCssExtractPlugin.loader,
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: [
                                    [
                                        require('autoprefixer')
                                    ]
                                ]
                            }

                        }
                    },
                    'sass-loader'
                ]
            },
         ]
     },
     plugins: [
     	new miniCssExtractPlugin()
     ]
 }

上面是对于scss的一个配置,我们在项目中的scss样式文件,会先经过 sass-loader 解析成css文件,然后通过了 postcss-loader 的解析,增加不同内核前缀,然后才是 css-loader 解析css文件,我这里最后是通过配置了环境变量来判断,如果是开发环境,使用style-loader , 正式环境进行了提取。

这样做的目的,是开发环境我们需要使用 style-loader 提供的开箱即用的hmr功能

我们会发现其实所有类型的文件配置的模板都差不多,可以举一反三,只不过不同类型的文件需要的 loader 有所区别,也有可能需要额外的一些配置,比如用 babel-loader 去配置js。

我们为什么要使用babel,其实很简单,由于浏览器的不统一,会造成我们的代码在不同浏览器支持效果不同,使用babel能实现向下兼容,并且,可以使用很多JavaScript新出的特性,而不用为浏览器是否支持而烦恼

在配置过程中发现,如果使用了vue-loader15,需要额外安装一个插件 vue-template-compiler,而且要注意这个插件的版本要和 vue 的版本保持一致

plugin

pluginloader 有什么具体的不同呢?为什么 loader 可以做文件转换后,我们还需要使用到 plugin 而且,它也是作为了webpack支持之一。

根据我的理解,首先他们针对的目标不同,loader 主要针对的是文件,可以理解为一个转换器,我们需要通过它来进行不同文件的编译转换。而 plugin 其实更偏向针对的是webpack,大家应该对机器人很感兴趣,如果我给机器人安装枪,那么机器人可以开火射击,安装光剑那可以劈砍,类似的 plugin 就相当于于不同的接口安装在webpack上面,来帮助我们更好的达成我们的目标。

webpack的插件生态很完整也很全,出场率最高的就是 html-webpack-plugin ,这里就用简单用一下它,其余的铁子们上 npmjs 查找吧。

// webpack.config.js
// 记得要先下载 html-webpack-plugin
module.exports = {
	...
	plugins: [
		new htmlWebpackPlugin({
			template: resolvePath(__dirname, '../public/index.html'),
			title: 'webpack5'
		})
	]
	...
}

这个插件的作用就是自动帮我们生成 index.html 文件,并且将我们打包完成后的javascript文件以及css样式文件都放入其中。免去了我们手动添加和修改的麻烦过程。

externals

外部扩展的目的是为了从输出的 bundle 中排除依赖项,通常我们使用的包模块会一起打包到输出文件中,这会导致我们的输出文件非常的巨大,从而导致加长了加载的时间,最终会造成白屏时间过长的问题。通过使用externals 我们能将项目中引入的不变的包模块 vue , react 等进行一下抽离。在运行时再去获取这些扩展依赖。

index.html 中我们可以引用外部的文件,例如 CDN 中。通过 script 标签将它们引入,然后在配置文件中进行配置

// index.html
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.js"></script>
// webpack.config.js
module.exports = {
	...
	externals: {
		'vue': 'Vue',
	}
}

这里的第一个 ‘vue’, 表示我们要引入 vue 的时候,所定义的代称。我们在文件中可能会这样使用, import vue from 'vue' ,后面的 ‘Vue’是表示 vue 模块所暴露出来的变量,可以去文件中查找。如此一来,我们就将 vue 模块从本应该在打包文件中的地方挪移出去了,减少了我们输出包的目标。

还有一个配置是 externalsType , 指定了我们引用的类型,如上面的代码,我们发现还需要在 index.html 中配置一个 script 标签,如果我们想通过打包自动的去引入标签,这个时候就需要配置一下 externalsType: 'script' 然后修改一下 externals 配置。

// webpack.config.js
module.exports = {
	...
	externals: {
		'vue':  [
			'https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.js',
            'Vue'
		],
	}
}

如此一来我们就不需要在 index.html 中去配置外部链接了。通过 externalsType 我们可以定义很多种类型,具体请看:externalsType更多类型

不过这里遇到一个问题,还没有解决,如果通过 externalsType: 'script'这种方式配置之后,在开启本地服务会报错。希望有明白的大佬能提供思路。

还需要注意一点,我们最好要把我们需要的模块放在项目所在服务器上,因为cdn也会挂掉,影响项目体验。

Resolve

resolve 配置可以让我们针对模块的解析进行颗粒度的配置,最常用的有两个选项。

  • alias
  • extensions

alias 解决一个路径问题,当我们的工作路径很深,在项目中引入文件写冗长的目录,既不美观,也不利于代码阅读。

// webpack.config.js
module.exports = {
	resolve: {
		alias: {
			// 根据开头的项目目录定制
			'@': path.resolve(__dirname, '../src')
		}
	}
}

这样我们在其他文件中就可以使用 import test from '@/views/test 的方式引入,实际上它引入的路径是 src/views/test

extensions 则是尝试按照定义的顺序解析后缀名。

// webpack.config.js
module.exports = {
	resolve: {
		extensions: ['.vue', '.js', '.json']
	}
}

比如我们引入一个模块,但是不写后缀名的时候。 import test from 'test' ,它就会按照我们定义的后缀名顺序解析,先查找是否有 test.vue , 然后是 test.js , 最后是 test.json

Optimization

其实从webpack4开始,就会根据项目所设定的mode来执行不同的优化策略,但是webpack更是支持自定义的优化策略。

可以使用 splitchunks 来进行一个公共模块提取和缓存。

// webpack.config.js
module.exports = {
	...
	optimization: {
        minimizer: [
            '...',
            new cssMinimizerWebpackPlugin()
        ],
        splitChunks: {
            cacheGroups: {
                vender: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendor',
                    chunks: 'all'
                }
            }
        }
    }
    ...
}

minimizer 可以覆盖webpack默认的打包插件,使用自己喜欢的插件来进行替换,如果只替换其中一个,剩余的访问默认值的话, 就使用 ··· 来占位替换。

splitChunks 中的配置,就是提取出在项目中引用 node_modules 的公共资源进行一个提取并放入缓存组,这样就可以将不改动的代码都缓存起来。

动态加载 | 预获取 | 预加载

这里还想提一下这个东西, 动态加载模块。通过esm的 import 来实现。动态加载会单独生成一个chunk进行输出。

// test.js
export let add = (a, b) => {
    return a + b;
}

export let bdd = (a, b) => {
    return a - b;
}
// main.js
const addFun = () => {
    return import('@/entry/test.js').then(res => {
        return res.add(1, 4)
    });
}
addFun().then((res) => {
    console.log(res)
})

我们在 entry 文件夹下创建了一个"test.js"文件,然后暴露出两个方法, 在入口文件进行引入。在打包的时候我们的打包目录就会多生成一个src_entry_test.js 文件这个名字是根据 output.filename 的规则定义的,如果我们想修改它的名字,可以添加注释。

// main.js
const addFun = () => {
    return import(/*webpackChunkName: 'test'*/'@/entry/test.js').then(res => {
        return res.add(1, 4)
    });
}

如上述代码,就是将名称修改成了 test

动态加载对于我们的项目来说有什么好处呢,一个是可以实现懒加载,再者webpack也提供给我们了预加载和预获取的功能。

  • prefetch(预获取):将来某些导航下可能需要用到的资源
  • preload(预加载):当前导航下可能需要的资源

两者的不同点就在于一个是当前的导航,一个是未来的导航。它们也是可以加在魔法注释中。

// main.js  --> prefetch
const addFun = () => {
    return import(/*webpackChunkName: 'test',webpackPrefetch: true*/'@/entry/test.js').then(res => {
        return res.add(1, 4)
    });
}

如此一来,我们就可以预获取资源 test 了, 浏览器会在网络的空闲期去主动下载,而不会一股脑的在开头讲文件全部下载下来,这样可以减少页面的空白期。
如果想改成预加载,只需要添加注释 webpackReload: true 就可以了。效果和懒加载类似。

devtool

从名字分析这东西肯定和我们的开发阶段有关系,是的没错。在我们开发的过程中,有时候需要看页面源码,和我们开发一样的代码,而不是注入了webpack的代码,这时候我们需要使用它。 它有如下几种常用类型

  • eval:每个module会封装到eval中执行,在末尾追加注释
  • source-map:生成一个sourceMap文件
  • hidden-source-map:和source-map一样,但是不会再末尾追加注释
  • inline-source-map:生成一个DataUrl形式的sourceMap文件
  • eval-source-map:每个module会封装到eval中执行,并且声称一个DataUrl形式的sourceMap
  • cheap-source-map:生成一个没有列信息的sourceMap,不包含loader的sourcemap
  • cheap-module-source-map:生成一个没有列信息的sourceMap,同事loader的sourcemap被简化为对应行。

sourcemap文件就是一个和我们源码的对照表,在我们开发过程中,项目出现什么问题需要定位的时候,会根据sourcemap文件中映射到我们真实的文件。通常我们在开发环境使用 cheap-module-source-map 比较好。

注意,这个配置只需要在开发环境进行配置,如果是生产环境要关掉 值改为false。其原因有两点。

  • 线上的产物通过sourcemap文件可能会被反编译出源码,有暴露的危险
  • sourcemap文件的体积较大,和我们生产环境所追求的代码轻量不符合。

webpack-dev-server

在使用webpack开发的时候,必不可少的就是 webpack-dev-server , 它为我们提供了一个本地服务的环境,模拟了一下用户访问时看到的项目最终运行效果。

// 下载webpack-dev-server
yarn add webpack-dev-server -D
// webpack.config.js
module.exports = {
	...
	devServer: {
		// 提供的静态资源文件夹
        static: _resolve(__dirname, '../dist'),
        // 服务的端口
        port: 9528,
        // 是否开启热模块替换
        hot: true,
        // 自动打开浏览器
        open: true,
        client: {
        	// 当有错误的时候在客户端进行覆盖显示
            overlay: true,
        },
        // 是否压缩代码
        compress: true,
        // 启用代理
        proxy: {
        	'base/api': {
        	target: 'http://www.baidu.com',
        	// 是否跨域
	        secure: true
        	}
        }
    },
}

配置之后我们可以在package.json中进行一个命令行的简化配置

// package.json
{
"scripts": {
    "start": "cross-env NODE_ENV=dev webpack server --config ./build/webpack.config.js",
    "build": "cross-env NODE_ENV=pro webpack --config ./build/webpack.config.js"
  },
 }

配置之后,我们就可以通过 npm run build 来进行打包。通过 npm run s 来开启本地服务器。

这里设置了一个环境变量 NODE_ENV ,通过使用模块 cross-env ,这个模块的主要功能是兼容不同系统上设定环境变量的写法。设定之后,我们在 webpack.config.js 中就可以通过 process.env.NODE_ENV 来访问我们设定的值了。

项目地址

https://gitee.com/monkey__wh/webpack_five

你可能感兴趣的:(前端工程化,webpack,前端,javascript)