共计 9706 个字符,预计需要花费 25 分钟才能阅读完成。
前言
系列文章
webpack4 从零开始构建(一)
webpack4+React16 项目构建(二)
webpack4 功能配置划分细化(三)
webpack4 引入 Ant Design 和 Typescript(四)
webpack4 代码去重, 简化信息和构建优化(五)
webpack4 配置 Vue 版脚手架(六)
因为之前我们已经花了五篇文章讲解了怎么从零配置一个 React 版的 Webpack 脚手架, 接下来我打算以前面代码为基础改成 Vue 版. 改变的第一步就是清除 React 全家桶和 Typescript 的痕迹.
清理工作
删除 node_modules
和tsconfig.json
用不上了
Package.json
删除不需要的依赖
{
"sideEffects": [
"*.scss",
"*.css"
],
"scripts": {
"dev": "cross-env NODE_ENV=DEV webpack --config ./config/webpack.dev.js",
"prod": "cross-env NODE_ENV=PROD webpack --config ./config/webpack.prod.js",
"start": "cross-env NODE_ENV=SERVER webpack-dev-server --config ./config/webpack.server.js",
"rnm": "rimraf node_modules"
},
"dependencies": { },
"devDependencies": {
"autoprefixer": "^9.4.10",
"babel-core": "^6.26.3",
"babel-loader": "7",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"clean-webpack-plugin": "^2.0.0",
"cross-env": "^5.2.0",
"css-loader": "^2.1.1",
"file-loader": "^3.0.1",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"image-webpack-loader": "^4.6.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-loader": "^3.0.0",
"progress-bar-webpack-plugin": "^1.12.1",
"rimraf": "^2.6.3",
"sass-loader": "^7.1.0",
"source-map-loader": "^0.2.4",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"webpack": "^4.30.0",
"webpack-bundle-analyzer": "^3.1.0",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.1",
"webpack-merge": "^4.2.1",
"webpack-parallel-uglify-plugin": "^1.1.0",
"xml-loader": "^1.2.1"
},
"name": "webpack_demo",
"version": "1.0.0",
"main": "index.tsx",
"license": "MIT"
}
configrules.js
只保留基本的处理规则
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const {isProd, isServer} = require('./env')
const cssMiniLoader = !isServer
? {
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
publicPath: process.env.NODE_ENV === "DEV" ? "./" : "../"
}
}
: "style-loader"; // 使用 <style> 将 css-loader 内部样式注入到我们的 HTML 页面,
const postcssLoader = {
loader: "postcss-loader",
options: {
config: {path: "./" // 写到目录即可,文件名强制要求是 postcss.config.js}
}
};
const imgLoader = {
loader: "url-loader",
options: {name: "[name].[hash:5].[ext]",
limit: 20 * 1024, // size <= 50kb
outputPath: "img"
}
};
module.exports = [
{
test: /\.s?css$/, // 匹配文件
use: [
cssMiniLoader,
"css-loader", // 加载.css 文件将其转换为 JS 模块
postcssLoader,
"sass-loader" // 加载 SASS / SCSS 文件并将其编译为 CSS
]
},
{test: /\.(png|svg|jpe?g|gif)$/i, // 图片处理
use:
isProd
? [
imgLoader,
{
loader: "image-webpack-loader",
options: {
// Compress JPEG images
mozjpeg: {
progressive: true,
quality: 65
},
// Compress PNG images
optipng: {enabled: false},
// Compress PNG images
pngquant: {
quality: "65-90",
speed: 4
},
// Compress GIF images
gifsicle: {interlaced: false}
}
}
]
: [imgLoader]
},
{test: /\.(woff|woff2|eot|ttf|otf)$/, // 字体处理
use: ["file-loader"]
},
{
test: /\.xml$/, // 文件处理
use: ["xml-loader"]
},
{test: /\.(html)$/,
use: {loader: "html-loader"}
}
];
src/*
保留目录, 除了图片和样式其他文件都删掉
安装基本依赖
yarn add vue vue-router vuex
yarn add --dev vue-loader vue-template-compiler
修改简化路径
const path = require("path");
// 创建 import 或 require 的别名,来确保模块引入变得更简单
module.exports = {"@": path.resolve(__dirname, "../src/"),
IMG: path.resolve(__dirname, "../src/img"),
STYLE: path.resolve(__dirname, "../src/style"),
JS: path.resolve(__dirname, "../src/js"),
ROUTER: path.resolve(__dirname, "../src/router"),
VUEX: path.resolve(__dirname, "../src/vuex"),
PAGE: path.resolve(__dirname, "../src/page"),
CMT: path.resolve(__dirname, "../src/component"),
// 'vue$':'vue/dist/vue.js'
};
最后一行大家可能有疑问, 如果不添加的话会这么输出
我们看 node_omdules
里 vue 仓库的 dist
目录, 里面有很多的构建版本
我们从它的 package.json 文件看到
"main": "dist/vue.runtime.common.js",
"module": "dist/vue.runtime.esm.js",
"unpkg": "dist/vue.js",
"jsdelivr": "dist/vue.js",
从官网我们找到这张图
术语 | 描述 |
---|---|
完整版本(Full) | 包含 编译器 (compiler) 和运行时 (runtime) 的构建版本 |
编译器(Compiler) | 负责将模板字符串编译成 JavaScript render 函数的代码 |
运行时(Runtime) | 负责 创建 Vue 实例 (creating Vue instances) 、 渲染 (rendering) 和修补虚拟 DOM(patching virtual DOM) 等的代码。基本上,等同于完整版本减去编译器 |
UMD | UMD 构建版本能够直接在浏览器中通过 <script> 标签使用。jsDelivr CDN 提供的默认文件 https://cdn.jsdelivr.net/npm/vue,是运行时 + 编译器 (Runtime + Compiler) 的 UMD 构建版本(vue.js) |
CommonJS | CommonJS 版本用于较早期的打包器 (bundler)(例如 browserify 或 webpack 1 等) 中。用于这些打包器的默认文件 (pkg.main),是只含有运行时(Runtime only) 的 CommonJS 构建版本(vue.runtime.common.js) |
ES Module | ES 模块版本构建用于现代打包器 (例如 webpack 2 或 rollup 等) 中。用于这些打包器的默认文件 (pkg.module),是只含有运行时(Runtime only) 的 ES Module 构建版本(vue.runtime.esm.js) |
在使用 vue-loader
或 vueify
时,*.vue
文件中的模板会 在构建时 (build time) 预编译 (pre-compile) 为 JavaScript。最终生成的 bundle 中你不再需要编译器(compiler),因此可以直接使用只含有运行时的构建版本(runtime-only)。
所以我们只要改一下初始化的方式就没必要添加路径使用完整版, 具体方式下面 src\index.js
和config/rules
文件配置会提到
更加具体的解释可以直接查看不同构建版本的解释说明
创建组件
srcpageview1.vue
<template>
<div>
<p>Page1</p>
<img
class="img1"
src='../img/1.jpg'
alt=""
/>
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
</style>
srcpageview2.vue
<template>
<div>
<p>Page2</p>
<div class="img2" />
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
</style>
srcApp.vue
<template>
<div id="app">
<router-link to="/view1">view1</router-link>
<router-link to="/view2">view2</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
srcrouterindex.js
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
let router = new Router({
routes: [
{
// 首页
path: '/view1',
component: () => import('CMT/view1')
},
{
path: '/view2',
component: () => import('CMT/view2')
},
{path: '*', redirect: '/view1'}
]
});
export default router;
srcvuexindex.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({state: {},
mutations: {}});
srcindex.js
// page
import 'STYLE/style.scss'
import Vue from 'vue';
import router from 'ROUTER/index.js';
import store from 'VUEX/index.js';
import App from './App';
new Vue({
el: '#root',
router,
store,
render: h => h(App)
});
修改 Webpack
configrules.js
主要变化是插入了对 ES 语法编译和 Vue 语法支持
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const {isProd, isServer} = require('./env')
const cssMiniLoader = !isServer
? {
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
publicPath: process.env.NODE_ENV === "DEV" ? "./" : "../"
}
}
: "style-loader"; // 使用 <style> 将 css-loader 内部样式注入到我们的 HTML 页面,
const postcssLoader = {
loader: "postcss-loader",
options: {
config: {path: "./" // 写到目录即可,文件名强制要求是 postcss.config.js}
}
};
const imgLoader = {
loader: "url-loader",
options: {name: "[name].[hash:5].[ext]",
limit: 20 * 1024, // size <= 50kb
outputPath: "img"
}
};
module.exports = [{
test: /\.js$/,
exclude: /node_modules/,
use: {loader: 'babel-loader'}
},
{test: /\.vue$/, use: 'vue-loader'},
{
test: /\.s?css$/, // 匹配文件
use: [
cssMiniLoader,
"css-loader", // 加载.css 文件将其转换为 JS 模块
postcssLoader,
"sass-loader" // 加载 SASS / SCSS 文件并将其编译为 CSS
]
},
{test: /\.(png|svg|jpe?g|gif)$/i, // 图片处理
use:
isProd
? [
imgLoader,
{
loader: "image-webpack-loader",
options: {
// Compress JPEG images
mozjpeg: {
progressive: true,
quality: 65
},
// Compress PNG images
optipng: {enabled: false},
// Compress PNG images
pngquant: {
quality: "65-90",
speed: 4
},
// Compress GIF images
gifsicle: {interlaced: false}
}
}
]
: [imgLoader]
},
{test: /\.(woff|woff2|eot|ttf|otf)$/, // 字体处理
use: ["file-loader"]
},
{
test: /\.xml$/, // 文件处理
use: ["xml-loader"]
},
{test: /\.(html)$/,
use: {loader: "html-loader"}
}
];
configwebpack.common.js
修改对应后缀扩展
extensions: ['.js', '.vue', '.json', 'scss', 'css']
configenv.js
改变入口地址
const path = require('path');
const isDev = process.env.NODE_ENV !== "DEV",
isProd = process.env.NODE_ENV !== "PROD",
isServer = process.env.NODE_ENV !== "SERVER",
entry = "./src/index.js",
outputName = "[name].bundle.js",
outputPath = path.resolve(__dirname, "../dist"),
publicPath = "",
title = "test";
module.exports = {
isDev,
isProd,
isServer,
entry,
outputName,
outputPath,
publicPath,
title
};
babelrc 配置详解
yarn add --dev babel-core babel-loader@7 babel-preset-env babel-preset-stage-2
之前只是粗略讲过它的一些基本情况, 现在可以单独讲讲它里面具体有什么东西, 除了之前说的
babel-core
是作为 babel 的核心, 把 javascript 代码分析成 AST (抽象语法树, 是源代码的抽象语法结构的树状表现形式),方便各个插件分析语法进行相应的处理
babel-loader
也是核心插件, 允许使用 Babel 和 webpack 转换 JavaScript 文件
初始的时候官方针对常用环境编写了一些 preset, 例如
preset | 作用 |
---|---|
babel-preset-es2015 | 可以将 es6 的代码编译成 es5 |
babel-preset-es2016 | 可以将 es7 的代码编译为 es6 |
babel-preset-es2017 | 可以将 es8 的代码编译为 es7 |
babel-preset-latest | 支持现有所有 ECMAScript 版本的新特性 |
古老写法
babel-polyfill
完整模拟 ES2015+ 环境, 一次性引入所有模块并且同项目代码一起编译到生产环境。而且会污染全局变量, 增加体积大概在 200~300K 左右
babel-runtime
Babel 使用了非常小的 helpers 来实现诸如 _extend
等常用功能。默认情况下,它将被添加到每个通过 require 引用它的文件中。这种重复 (操作) 有时是不必要的,特别是当你的应用程序被拆分为多个文件时。
babel-plugin-transform-runtime
所有的 helper 都会引用模块 babel-runtime
,以避免编译输出的重复问题。这个运行时会被编译到你的构建版本当中。另外一个目的就是为你的代码创建一个沙盒环境将内置插件起了别名
{
// 插件
"plugins": [
[
"transform-runtime",
{"helpers": false, // 是否切换将内联 (inline) 的 Babel helper(classCallCheck,extends 等)替换为对 moduleName 的调用。"polyfill": false, // 是否切换新的内置插件 (Promise,Set,Map 等) 为使用非全局污染的 polyfill。"regenerator": true, // 是否切换 generator 函数为不污染全局作用域的 regenerator 运行时。"moduleName": "babel-runtime" // 当引入 helper 时,设置要使用的模块的名称 / 路径。}
]
],
}
新写法
babel-preset-env
但是随着历史进程, 越来越多的 preset 出现不便于开发配置, 于是推出了 babel-preset-env
基于你的实际浏览器及运行环境,自动的确定 babel 插件及 polyfills,转译 ES2015 及此版本以上的语言, 默认配置情况下和 babel-preset-latest
一致
我们直接配置兼容的版本情况支持每个浏览器最后两个版本和 safari 大于等于 7 版本所需的 polyfill 代码转换
但是 babel-preset-env
已提供方法可以替代上面 babel-plugin-transform-runtime
类似的作用了
// 预设
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": [
"last 2 versions",
"safari >= 7"
]
},
"useBuiltIns": "usage" // "usage" | "entry" | false, 默认为 false
}
]
],
因为没有太深入研究, 具体有些差别不太清楚
babel-preset-stage-x
TC39 将提案分为以下几个阶段:
阶段 | 描述 |
---|---|
Stage 0 – 设想(Strawman) | 只是一个想法,可能有 Babel 插件 |
Stage 1 – 建议(Proposal) | 这是值得跟进的 |
Stage 2 – 草案(Draft) | 初始规范 |
Stage 3 – 候选(Candidate) | 完成规范并在浏览器上初步实现 |
Stage 4 – 完成(Finished) | 将添加到下一个年度版本发布中 |
因为 babel-preset-env
只支持最新推出版本的 JavaScript 语法(state-4), 所以如果想要体验部分还为完成阶段的新语法可以配置对应的插件转换, 一般主流推荐使用 2.
// 预设
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": [
"last 2 versions",
"safari >= 7"
]
},
"useBuiltIns": "usage" // "usage" | "entry" | false, 默认为 false
}
]
"stage-2"
],
还有部分暂时没用上的东西
{
// 预设
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": [
"last 2 versions",
"safari >= 7"
]
},
"useBuiltIns": "usage" // "usage" | "entry" | false, 默认为 false
}
],
"stage-2"
],
// 插件
"plugins": [],
/*
设置特定的配置选项
env 选项的值将从 process.env.BABEL_ENV 获取,如果没有的话,则获取 process.env.NODE_ENV 的值,它也无法获取时会设置为 "development"
*/
"env": {"development": {},
"production": {}}
}
配置插件
如果以当前配置运行命令
yarn start
终端会输出错误如图
所以我们需要根据错误提示引入 Vue-loader
插件
configwebpack.common.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');
------------------ 省略 -----------------------
plugins: [new VueLoaderPlugin(),
],
再次运行即可正常.