webpack的原理和手动实践实现webpack后续再进行补充。
在vue/cli 3 中为减少使用者对webpack繁杂的配置项,将webpack集成在cli内部,同时提供vue.config.js文件允许用户进行配置。
为涵盖了使用 vue-cli 开发过程中大部分配置需求,下面细致全面的总结了 vue-cli3 配置信息。
1.配置多环境变量
在package.json中指定 –mode来配置不同环境的命令项;
只有以 VUE_APP 开头的变量会被 webpack.DefinePlugin 静态嵌入到客户端侧的包中,代码中可以通过 process.env.VUE_APP_BASE_API 访问
NODE_ENV 和 BASE_URL 是两个特殊变量,在代码中始终可用;
可以通过process来访问环境变量
javascript
console.log("BASE_URL", process.env.BASE_URL);
console.log("VUE_APP_API", process.env.VUE_APP_API);
配置:
在项目的根目录中新建环境变量文件(.env, .env.production, .env.analyz等)
- .env
javascript
// 即: npm run serve 默认的本地开发环境配置
NODE_ENV = "development"
BASE_URL = "./"
VUE_APP_PUBLIC_PATH = './'
VUE_APP_API = "https://development.com/api" //开发环境域名
- .env.production
javascript
// 即:build 默认的环境配置
NODE_ENV = 'production'
BASE_URL = 'https://production.com/'
VUE_APP_PUBLIC_PATH = 'https://production/blog'
VUE_APP_API = 'https://production/api'
ACCESS_KEY_ID = 'xxxxxxxxxxxxx'
ACCESS_KEY_SECRET = 'xxxxxxxxxxxxx'
- .env.analyz
javascript
// 即:自定义的build环境配置
NODE_ENV = 'production'
BASE_URL = 'https://production.com/'
VUE_APP_PUBLIC_PATH = https://production.com/blog'
VUE_APP_API = 'https://production.com/api'
ACCESS_KEY_ID = 'xxxxxxxxxxxxx'
ACCESS_KEY_SECRET = 'xxxxxxxxxxxxx'
IS_ANALYZE = true
修改package.json文件:
javascript
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"analyz": "vue-cli-service build --mode analyz",
"lint": "vue-cli-service lint"
},
2.配置基础的vue.config.js
区别开发和生产环境,并进行相应的配置(其他的配置后文循序渐进完善)
javascript
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
module.exports = {
publicPath: IS_PROD ? process.env.VUE_APP_PUBLIC_PATH : './', // 默认'/',部署应用包时的基本 URL
// outputDir: process.env.outputDir || 'dist', // 'dist', 生产环境的构建文件的目录,可以在环境变量中配置
// assetsDir: "", // 相对于outputDir的静态资源(js、css、img、fonts)目录
lintOnSave: false,
runtimeCompiler: true, // 是否使用包含运行时编译器的 Vue 构建版本
productionSourceMap: !IS_PROD, // 生产环境的 source map,开发开启,生产关闭
parallel: require('os').cpus().length > 1,
pwa: {}
}
3.配置 proxy 代理
配置代理可用于解决开发中的跨域的问题。
devServer.proxy
可以是一个指向开发环境 API 服务器的字符串,也可以使用一个 path: options
成对的对象;
javascript
module.exports = {
devServer: {
// https: true, //当proxy中为https时,需要设置开启
proxy: 'http://localhost:4000'
}
}
path:options的方式:
javascript
module.exports = {
devServer: {
// overlay: { // 让浏览器 overlay 同时显示警告和错误
// warnings: true,
// errors: true
// },
// open: false, // 是否打开浏览器
// host: "localhost",
// port: "8080", // 代理断就
// https: false,
// hotOnly: false, // 热更新
proxy: {
'/api': {
target:
'https://www.easy-mock.com/mock/5bc75b55dc36971c160cad1b/sheets', // 目标代理接口地址
secure: false,
changeOrigin: true, // 开启代理,在本地创建一个虚拟服务端
// ws: true, // 是否启用websockets
pathRewrite: {
'^/api': '/'
}
}
}
}
}
4.配置别名
配置项目的别名,方便文件的引入,特别一些自定义的文件
javascript
const path = require('path')
const resolve = dir => path.join(__dirname, dir)
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
module.exports = {
chainWebpack: config => {
// 添加别名
config.resolve.alias
.set('vue$', 'vue/dist/vue.esm.js')
.set('@', resolve('src'))
.set('@assets', resolve('src/assets'))
.set('@scss', resolve('src/assets/scss'))
.set('@components', resolve('src/components'))
.set('@plugins', resolve('src/plugins'))
.set('@views', resolve('src/views'))
.set('@router', resolve('src/router'))
.set('@store', resolve('src/store'))
.set('@layouts', resolve('src/layouts'))
.set('@static', resolve('src/static'))
}
}
5. 图片的打包处理
对于vue/cli 3默认的url-loader,默认是对小于4096字节的图片进行base64转化,对于大图片我们采用image-webpack-loader来进行压缩;
javascript
// 添加图片压缩的loader
npm i -D image-webpack-loader
补充:如果使用npm安装后,运行失败报错缺少依赖,例如:
javascript
Module build failed (from ./node_modules/image-webpack-loader/index.js):
Error: Cannot find module 'gifsicle'
猜测可能是网络的原因,采用淘宝镜像进行安装:
javascript
// 先卸载
npm uninstall image-webpack-loader
// 添加淘宝镜像
npm install cnpm -g --registry=https://registry.npm.taobao.org
// 再次安装
cnpm install --save-dev image-webpack-loader
在vue.config.js中添加配置:关于image-webpack-loader的配置信息可以查看官网:www.npmjs.com/package/ima…,关于chainWebpack的信息可以查看:chainWebpack
javascript
module.exports = {
chainWebpack: config => {
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
mozjpeg: { progressive: true, quality: 65 },
optipng: { enabled: false },
pngquant: { quality: [0.65, 0.9], speed: 4 },
gifsicle: { interlaced: false },
webp: { quality: 75 }
})
}
}
6. 自动生成雪碧图
默认 src/assets/icons 中存放需要生成雪碧图的 png 文件。首次运行 npm run serve/build 会生成雪碧图,并在根目录生成 icons.json 文件。再次运行命令时,会对比 icons 目录内文件与 icons.json 的匹配关系,确定是否需要再次执行 webpack-spritesmith 插件。
javascript
// 添加 plugin
npm i -D webpack-spritesmith
修改配置文件:vue.config.js
const SpritesmithPlugin = require('webpack-spritesmith')
const path = require('path')
const fs = require('fs')
let has_sprite = true;
let files = [];
const icons = {};
try {
fs.statSync(resolve("./src/assets/icons"));
files = fs.readdirSync(resolve("./src/assets/icons"));
files.forEach(item => {
let filename = item.toLocaleLowerCase().replace(/_/g, "-");
icons[filename] = true;
});
} catch (error) {
fs.mkdirSync(resolve("./src/assets/icons"));
}
if (!files.length) {
has_sprite = false;
} else {
try {
let iconsObj = fs.readFileSync(resolve("./icons.json"), "utf8");
iconsObj = JSON.parse(iconsObj);
has_sprite = files.some(item => {
let filename = item.toLocaleLowerCase().replace(/_/g, "-");
return !iconsObj[filename];
});
if (has_sprite) {
fs.writeFileSync(resolve("./icons.json"), JSON.stringify(icons, null, 2));
}
} catch (error) {
fs.writeFileSync(resolve("./icons.json"), JSON.stringify(icons, null, 2));
has_sprite = true;
}
}
// 雪碧图样式处理模板
const SpritesmithTemplate = function(data) {
// pc
let icons = {};
let tpl = `.ico {
display: inline-block;
background-image: url(${data.sprites[0].image});
background-size: ${data.spritesheet.width}px ${data.spritesheet.height}px;
}`;
data.sprites.forEach(sprite => {
const name = "" + sprite.name.toLocaleLowerCase().replace(/_/g, "-");
icons[`${name}.png`] = true;
tpl = `${tpl}
.ico-${name}{
width: ${sprite.width}px;
height: ${sprite.height}px;
background-position: ${sprite.offset_x}px ${sprite.offset_y}px;
}
`;
});
return tpl;
};
module.exports = {
configureWebpack: config => {
const plugins = [];
if (has_sprite) {
plugins.push(
new SpritesmithPlugin({
src: {
cwd: path.resolve(__dirname, "./src/assets/icons/"), // 图标根路径
glob: "**/*.png" // 匹配任意 png 图标
},
target: {
image: path.resolve(__dirname, "./src/assets/images/sprites.png"), // 生成雪碧图目标路径与名称
// 设置生成CSS背景及其定位的文件或方式
css: [
[
path.resolve(__dirname, "./src/assets/scss/sprites.scss"),
{
format: "function_based_template"
}
]
]
},
customTemplates: {
function_based_template: SpritesmithTemplate
},
apiOptions: {
cssImageRef: "../images/sprites.png" // css文件中引用雪碧图的相对位置路径配置
},
spritesmithOptions: {
padding: 2
}
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
};
**注意点:**替换icons中得图片名来更改图片,可能会造成雪碧图不更新;避免使用同名的图片;
7.SVG图片转成font字体
添加依赖:
npm i -D svgtofont
在项目的根目录中新增scripts目录,并新建svg2font.js文件
缺点:转化后的font字体,每一个仅支持一种颜色;若需要保留svg的设计稿,建议采用
const svgtofont = require('svgtofont')
const path = require('path')
const pkg = require('../package.json')
svgtofont({
src: path.resolve(process.cwd(), "src/assets/svg"), //svg图标目录路径
dist: path.resolve(process.cwd(), "src/assets/fonts"), //输出到指定目录
fontName: 'icon', //设置字体名称
css: true, //生成字体文件
startNumber: 20000, // unicode起始编号
svgicons2svgfont: {
fontHeight:1000,
normalize: true
},
website: {
title: 'icon',
logo: '',
version: pkg.version,
meta: {
description: '',
keywords: ''
},
description: '',
links: [
{
title: 'Font Class',
url: "index.html"
},
{
title: "Unicode",
url: 'unicode.html'
}
],
footerInfo: ``
}
}).then(()=>{
console.log('Successful transformation, congratulations!')
}).catch(err=>{
console.log(err)
})
**使用:**在mian.js中引入src/assets/fonts下的icon.css;使用可以好看同文件下的html文件
8.使用SVG组件
为了弥补font的缺点,采用svg全局组件的方式;
//添加loader
npm i -D svg-sprite-loader
添加公共组件: /src/components/common/SvgIcon.vue
<template>
<svg class="svg-icon" :style="iconStyle" aria-hidden="true">
<use :xlink:href="name"/>
</svg>
</template>
<script>
export default {
// iconName: 需要展示的svg图片名
// iconStyle: 自定义的行类样式
name: 'SvgIcon',
props: {
iconName: {
type: String,
required: true
},
iconStyle: {
type:String,
default: ''
}
},
computed: {
name() {
return `#icon-${this.iconName}`
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
添加svg资源和相应的脚本:在src下创建icons文件夹,用于处理svg的组件和逻辑;在icons文件下添加svg文件放svg图片,以及添加index.js
import SvgIcon from "@/components/common/SvgIcon";
import Vue from "vue";
/* require.context("./test", false, /.test.js$/);
这行代码就会去 test 文件夹(不包含子目录) 下面的找所有文件名以 .test.js 结尾的文件能被 require 的文件。
更直白的说就是 我们可以通过正则匹配引入相应的文件模块。
require.context有三个参数:
directory:说明需要检索的目录
useSubdirectories:是否检索子目录
regExp: 匹配文件的正则表达式 */
// 注册到全局
Vue.component("svg-icon", SvgIcon);
const requireAll = requireContext => requireContext.keys().map(requireContext);
const req = require.context("./svg", false, /\.svg$/);
requireAll(req);
在main.js中引入:
import "@/icons/index.js";
添加svg的loader配置,修改vue.config.js
const path = require("path");
const resolve = dir => path.join(__dirname, dir);
module.exports = {
chainWebpack: config => {
const svgRule = config.module.rule("svg");
svgRule.uses.clear();
svgRule.exclude.add(/node_modules/);
svgRule
.test(/\.svg$/)
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({
symbolId: "icon-[name]"
});
const imagesRule = config.module.rule("images");
imagesRule.exclude.add(resolve("src/icons"));
config.module.rule("images").test(/\.(png|jpe?g|gif|svg)(\?.*)?$/);
}
};
9.添加打包分析
vue/cli 3中已包含依赖,直接在vue.config.js中修改
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
module.exports = {
chainWebpack: config => {
// 打包分析
if (IS_PROD) {
config.plugin("webpack-report").use(BundleAnalyzerPlugin, [
{
analyzerMode: "static"
}
]);
}
}
};
10.开启gzip压缩
// 添加依赖
npm i -D compression-webpack-plugin
修改vue.config.js
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;
module.exports = {
configureWebpack: config => {
const plugins = [];
if (IS_PROD) {
plugins.push(
new CompressionWebpackPlugin({
algorithm: "gzip",
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8
})
);
}
config.plugins = [...config.plugins, ...plugins];
}
};
11.生产环境去除console.log
使用 babel-plugin-transform-remove-console 插件
npm i -D babel-plugin-transform-remove-console
在babel.config.js中添加配置,没有这个文件则在根目录新建一个:
const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV);
const plugins = [];
if (IS_PROD) {
plugins.push("transform-remove-console");
}
module.exports = {
presets: ["@vue/app", { useBuiltIns: "entry" }],
plugins
};
12.添加IE兼容
npm i -S @babel/polyfill
在 main.js 中添加
import "@babel/polyfill";
配置 babel.config.js
const plugins = [];
module.exports = {
presets: [["@vue/app", { useBuiltIns: "entry" }]],
plugins: plugins
};
13. SplitChunksPlugin
很强大,也特别有用的插件;详细可参考看:juejin.im/post/684490…
14. externals缓存第三方资源
使用externals插件排除一些固定资源,使之不会打包到dist中;一般是结合cdn进行使用;配置也比较简单,此处只放官网;webpack.js.org/configurati…
15.加快编译 hard-source-webpack-plugin
npm地址: www.npmjs.com/package/har…
作用:第一次构建插件就会默认把缓存结果存到node_modules下的.cache目录下,第二次构建的时候再取出缓存使用。故可以加过构建的速度
作者:小灰灰同学
链接:https://juejin.im/post/6893885335018110983
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com