|
il y a 2 ans | |
---|---|---|
.. | ||
config | il y a 2 ans | |
public | il y a 2 ans | |
src | il y a 2 ans | |
.eslintrc.js | il y a 2 ans | |
.gitignore | il y a 2 ans | |
.prettierrc.json | il y a 2 ans | |
.yarnrc | il y a 2 ans | |
README.md | il y a 2 ans | |
babel.config.js | il y a 2 ans | |
jsconfig.json | il y a 2 ans | |
package-lock.json | il y a 2 ans | |
package.json | il y a 2 ans | |
postcss.config.js | il y a 2 ans | |
yarn.lock | il y a 2 ans |
从不同阶段的角度来划分,不同的阶段所需要的功能不同,因此整体构建可划分为:本地开发阶段、生产部署阶段
开发阶段: 需要本地启动服务器环境,支持代码即时预览、模块热更新、devtool 调试功能等;
生产部署阶段: 需要开启压缩优化、静态资源复制目录、代码 tree shaking、chunk 抽离等功能;阶段不同所需功能不同。
不同阶段所需功能不同,但工具的基础能力一致,因此可将整体构建拆分为如下结构:
基础通用功能
开发阶段配置
生产部署阶段
想看完整配置文件小伙伴可直接移步文章末尾
预定使用以下目录结构
webpack 构建相关配置目录
webpack.prod.js 生产环境配置
public 公共通用的静态资源目录
src
.eslintrc.js
代码风格配置文件
工具 babel 转换配置,设置预设等
css 样式转换配置
项目打包需要依赖 webpack 且需要在命令行执行,因此先行安装 webpck、webpack-cli 到开发依赖
npm i webpack webpack-cli --save-dev
安装完毕后在 webpack.common.js 中增加通用配置
module.exports = {
entry: "./src/main.js", // 默认是此目录,如若文件名称或者入口变更可修改
};
const resolveApp = require("./paths");
module.exports = {
output: {
path: resolveApp("dist"),
filename: "js/[name].[hash:6].js",
chunkFilename: "js/[name].chunk.[hash:4].js",
},
};
paths.js 文件内容
const path = require("path"); // webpack内置模块
const appRoot = process.cwd(); // 命令行运行的根目录
const resolveApp = (resolvePath) => {
return path.resolve(appRoot, resolvePath); // 获取指定目录的完整绝对路径
};
module.exports = resolveApp;
path:文件输出到 dist 目录中,根目录路径的获取通过命令行终端函数获取
filename:构建后的脚本文件输出到 js 目录下,且使用动态文件名称,附带文件 6 为 hash 值
chunkFilename:构建时拆分出来的 chunk 文件同样放入 js 目录下,且名称除了以上规则外,增加 chunk 标识
在模块源码中,通过 import 加载其他模块时需要写出其前缀目录地址,如若嵌套较深则会书写麻烦,此时可以配置特定路径标识映射,以简化此路径。
同时,导入其他模块时默认在文件末尾增加 js 文件格式结尾,其他文件格式会无法找到,配置自动补全规则来增加其他文件格式补全
module.exports = {
resolve: {
extensions: [".js", ".jsx", ".vue", ".json", "..."],
alias: {
"@": resolveApp("src"),
},
},
};
resolve:通过extensions
配置自动补全的文件类型
resolve:通过alias
配置简化路径的映射标识
外部依赖的库文件如若较大且不会频繁变动,则可将库文件的导入交给外部,如使用外链形式走 cdn 加速、构建 dll 库或其他形式,通过 html 页面增加 script 脚本引入库文件
module.exports = {
externals: {
// 不希望依赖打进包中,走外链cdn等
// '$': 'Jquery',
// react: 'React',
// 'react-dom': 'ReactDOM',
// 'prop-types': 'PropTypes',
// moment: 'moment',
// antd: 'antd',
// classnames: 'classNames'
},
};
1.webpack 默认只能处理 js 文件,而我们项目中不可避免要用到样式表 css、图片 imgage、字体 font 等资源,而这类的资源文件 webpack 并不认识,因此无法直接处理
2.js 模块中我们会用到最新的 ES6+语法,也可能会用到 coffee script 语法,或者严谨的 typescript 语法等,这些语法在浏览器环境中支持程度不一,或不支持直接解析。
基于以上种种原因,我们需要通过 loader 加载器来将这类资源文件转换为可以被识别并解析的标准 js
安装
先安装 vue 到项目依赖,再安装 vue-loader、vue-template-compiler 到开发依赖
vue-template-compiler 的作用是将 vue 的模板代码预编译为渲染函数,以避免在运行时编译开销
npm i vue --save
npm i vue-loader vue-template-compiler --save-dev
使用
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
use: "vue-loader",
},
],
},
plugins: [new VueLoaderPlugin()],
};
安装完毕后 rules 内添加相应规则,检测 vue 结尾的文件,并使用 vue-loader。同时要求单独导出 vueLoaderPlugin,在 plugins 插件集合内实例化,以此来正确解析 vue 文件
对于样式表 css 的处理和其他 vue、js 处理不同,样式表 css 处理需要经过多步骤处理后才能正常使用,具体流程为:
1.使用 postcss-loader 将 css 文件内样式根据浏览器支持情况转换为多平台兼容写法,如使用 autoprefixer、browserslist、caniuse-lite 等依赖增加平台特性写法;
2.接着将转换后的样式表 css 交由 css-loader 处理 import、url 等行为,将引入和依赖的外部资源加载到当前样式表中;
3.处理完毕后将 css 交由 style-loader,已 style 标签的形式注入 html 页面中
这里存在一个问题,开发环境时将 css 全部注入页面没有影响,但生产环境会是多文件大量内容,超出量后会导致首页加载变慢。因此需要在生产阶段将 css 单独抽离出文件,通过判断环境来加载相应配置,将 module.exports 导出内容变更为函数,接受环境标识
安装 loader
npm i postcss-loader css-loader style-loader --save-dev
安装插件 plugin
npm i mini-css-extract-plugin --save-dev
使用
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = (isProduction) => {
const cssFinalLoader = isProduction
? MiniCssExtractPlugin.loader
: "style-loader";
return {
module: {
rules: [
{
test: /\.css$/,
use: [
cssFinalLoader, // 开发与生产使用不同loader
{
loader: "css-loader",
options: {
esModule: false, // css不使用esModule,直接输出
importLoaders: 1, // 使用本loader前使用1个其他处理器
},
},
"postcss-loader",
],
sideEffects: true, // 希望保留副作用
},
],
},
};
};
前边提到了 postcss-loader 会根据支持度情况自动转换 css 内容,postcss-loader 作为转换的一个平台,会提供插槽的能力,但是转换的具体规则并不会去做,因此这个转换规则需要我们通过其他插件解决。这里我们使用一个预设的转换集合postcss-preset-env
安装
npm i postcss-preset-env --save-dev
使用
postcss.config.js 文件
module.exports = {
plugins: [require("postcss-preset-env")],
};
less-loader 与 css-loader 处理逻辑相似,区别为:处理前需要先使用 less-loader 将 less 文件处理为 css 文件,处理 less 文件依赖 less 包。
安装
npm i less less-loader --save-dev
配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = (isProduction) => {
const cssFinalLoader = isProduction
? MiniCssExtractPlugin.loader
: "style-loader";
return {
module: {
rules: [
{
test: /\.less$/,
use: [
cssFinalLoader,
{
loader: "css-loader",
options: {
importLoaders: 2,
},
},
"postcss-loader",
"less-loader",
],
},
],
},
};
};
在 webpack4 版本中,图片类资源是通过 file-loader 和 url-loader 来配合实现。但 webpack5 版本中官方内置了 asset 来处理静态资源类。
配置
module.exports = (isProduction) => {
return {
module: {
rules: [
{
test: /\.(png|gif|jpe?g|svg)$/,
type: "asset", // webpack5使用内置静态资源模块,且不指定具体,根据以下规则使用
generator: {
filename: "img/[name].[hash:6][ext]", // ext本身会附带点,放入img目录下
},
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 超过10kb的进行复制,不超过则直接使用base64
},
},
},
],
},
};
};
配置
与图片类资源处理相似,但去除了大小限制,因字体文件是整体,且无需额外处理,因此直接进行静态资源复制
module.exports = (isProduction) => {
return {
module: {
rules: [
{
test: /\.(ttf|woff2?|eot)$/,
type: "asset/resource", // 指定静态资源类复制
generator: {
filename: "font/[name].[hash:6][ext]", // 放入font目录下
},
},
],
},
};
};
js 模块的转换操作需要依赖 babel-loader,与 postcss-loader 类似,babel-loader 也可以理解为一个转换的平台,并不做具体转换规则,因此需要依赖@babel/core
、@babel/preset-env
、@vue/cli-plugin-babel
插件来完成转换
安装
npm i @babel/core @babel/preset-env @vue/cli-plugin-babel --save-dev
配置
module.exports = (isProduction) => {
return {
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/, // 过滤掉node_modules目录,只使用而已
use: "babel-loader", // js、jsx使用bable-loader处理
},
],
},
};
};
进行语法转换的时候需要排除掉 node_modules 目录
babel.config.js 文件
module.exports = {
presets: [
"@vue/cli-plugin-babel/preset",
[
"@babel/preset-env",
{
useBuiltIns: "usage", // 用到什么填充什么,按需
corejs: 3, // 默认是2版本
},
],
],
};
代码风格检查
安装
npm i eslint eslint-loader --save-dev
配置
npx eslint --init // 根据提示完成配置,详情见webpack打包-上
执行完毕后会安装相关依赖和生成.eslintrc.js
配置文件
.eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: ["plugin:vue/essential", "standard"],
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["vue"],
rules: {},
};
基础功能内提供全局变量定义、页面模板、vue 相关 plugin 等配置
module.exports = (isProduction) => {
return {
plugins: [
new DefinePlugin({
BASE_URL: '"./"',
}),
new HtmlWebpackPlugin({
title: "贾贵山-自建构建打包流程",
template: "public/index.html",
}),
new VueLoaderPlugin(),
],
};
};
const { DefinePlugin } = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
const resolveApp = require("./paths");
module.exports = (isProduction) => {
const cssFinalLoader = isProduction
? MiniCssExtractPlugin.loader
: "style-loader";
return {
entry: "./src/main.js",
output: {
path: resolveApp("dist"),
filename: "js/[name].[hash:6].js",
chunkFilename: "js/[name].chunk.[hash:4].js",
},
resolve: {
extensions: [".js", ".jsx", ".vue", ".json", "..."],
alias: {
"@": resolveApp("src"),
},
},
externals: {
// 不希望依赖打进包中,走外链cdn等
// '$': 'Jquery',
// react: 'React',
// 'react-dom': 'ReactDOM',
// 'prop-types': 'PropTypes',
// moment: 'moment',
// antd: 'antd',
// classnames: 'classNames',
},
module: {
rules: [
// vue文件处理
{
test: /\.vue$/,
use: "vue-loader",
},
// less文件处理
{
test: /\.less$/,
use: [
cssFinalLoader,
{
loader: "css-loader",
options: {
importLoaders: 2,
},
},
"postcss-loader",
"less-loader",
],
},
// css文件处理
{
test: /\.css$/,
use: [
cssFinalLoader, // 最终要以style标签输出到页面
{
loader: "css-loader",
options: {
esModule: false, // css不使用esModule,直接输出
importLoaders: 1, // 使用本loader前使用1个其他处理器
},
},
"postcss-loader",
],
sideEffects: true, // 希望保留副作用
},
// 图片类处理
{
test: /\.(png|gif|jpe?g|svg)$/,
type: "asset", // webpack5使用内置静态资源模块,且不指定具体,根据以下规则 使用
generator: {
filename: "img/[name].[hash:6][ext]", // ext本身会附带 点,放入img目录下
},
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 超过10kb的进行复制,不超过则直接使用base64
},
},
},
// 字体类处理
{
test: /\.(ttf|woff2?|eot)$/,
type: "asset/resource", // 指定静态资源类复制
generator: {
filename: "font/[name].[hash:6][ext]", // 放入font目录下
},
},
// 脚本类处理
{
test: /\.jsx?$/,
exclude: /node_modules/, // 过滤掉node_modules目录,只使用而已
use: "babel-loader", // js、jsx使用bable-loader处理
},
{
test: /\.(vue|js)$/,
use: "eslint-loader",
exclude: /node_modules/,
enforce: "pre",
},
],
},
plugins: [
new DefinePlugin({
BASE_URL: '"./"',
}),
new HtmlWebpackPlugin({
title: "贾贵山-自建构建打包流程",
template: "public/index.html",
}),
new VueLoaderPlugin(),
],
optimization: {
runtimeChunk: true, // 模块抽取,利用浏览器缓存
minimizer: [
new TerserWebpackPlugin({
extractComments: false, // 不要注释生成的文件
}),
],
},
};
};
因开发环境在通用配置基础上配置,因此直接展示完整示例,对相应配置进行说明
安装
npm i webpack-merge webpack-dev-server --save-dev
配置
const baseConfig = require("./webpack.common");
const { merge } = require("webpack-merge");
module.exports = merge(baseConfig(false), {
mode: "development",
devtool: "cheap-module-source-map",
target: "web",
devServer: {
hot: "only",
port: 3002, // 端口号,工作中从3001开始,因此增加1个到3002
open: true, // 自动打开浏览器
compress: true, // 开启gzip压缩
historyApiFallback: true, // history路径在刷新出错时重定向开启
proxy: {
// 接口代理
"/api": {
// 统一api前缀都代理掉
target: "http://api.github.com", // 代理的目标地址
changeOrigin: true, // 改变来源信息
pathRewrite: {
// 因前缀为自己增加,因此重写地址
"/api": "", // 将前缀去掉
},
},
},
},
optimization: {
chunkIds: "named",
},
});
webpack-merge
包进行配置合并development
cheap-module-source-map
const baseConfig = require("./webpack.common");
const { merge } = require("webpack-merge");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const webpack = require("webpack");
const PurgeCSSPlugin = require("purgecss-webpack-plugin");
const resolveApp = require("./paths");
const glob = require("glob");
const CompressionPlugin = require("compression-webpack-plugin");
// var InlineChunkHtmlPlugin = require('inline-chunk-html-plugin');
// const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(baseConfig(true), {
mode: "production",
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: "public",
to: "public",
globOptions: {
ignore: ["**/index.html"],
},
},
],
}),
new MiniCssExtractPlugin({
// css单独拆分为文件
filename: "css/[name].[hash:6].css",
}),
new webpack.optimize.ModuleConcatenationPlugin(), // 作用域提升,提升性能
new PurgeCSSPlugin({
paths: glob.sync(`${resolveApp("./src")}/**/*`, { nodir: true }),
}),
new CompressionPlugin({
test: /\.(css|js)$/i,
algorithm: "gzip",
}),
// new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime/]) // 将一定大小文件直接注入 html,
],
optimization: {
chunkIds: "deterministic", // 文件名称尽可能短,也会是序号类型
splitChunks: {
chunks: "all",
minSize: 20000, // 拆分的每个包不小于20kb
maxSize: 20000, // 体积大于设置的值的包要去拆分开包
minChunks: 1, // 包如果要拆分,则必须要至少引用一次
cacheGroups: {
syVendors: {
test: /[\\/]node_modules[\\/]/, // 对目录内文件进行单独打包拆分,且放入一个文件中 vender
filename: "js/[id]_verdor.js", // 最终名字
priority: -10, // 都满足时候的优先级,越高月用
},
},
},
minimizer: [new CssMinimizerPlugin()],
},
});
生产环境依然采用合并基础配置方式
production
模式const devConfig = require("./webpack.dev");
const prodConfig = require("./webpack.prod");
// const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
// const smp = new SpeedMeasurePlugin();
module.exports = (env) => {
// const finalResult = env.production ? prodConfig : devConfig;
// console.log('final---->', finalResult)
// return smp.wrap(finalResult);
return env.production ? prodConfig : devConfig;
};
构建时减少繁琐修改配置文件的操作,在 package.json 内添加启动参数来选择不同环境配置
git clone https://gitee.com/tammylights/fed-e-task-02-02.git
cd fed-e-task-02-02
npm install
npm run serve
npm run build
本地开发则启动 npm run serve
构建生产环境则 npm run build
检查代码风格则 npm run lint