1.什么是vue loader
Vue Loader 是一个 webpack 的 loader,它可以将.vue文件转换为webpack能够处理的有效模块:
<template>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello world!'
}
}
}
</script>
<style>
.example {
color: red;
}
</style>
2.安装和webpack配置
安装vue-loader
和 vue-template-compiler
应该一起安装。
每个 vue 包的新版本发布时,一个相应版本的 vue-template-compiler
也会随之发布。编译器的版本必须和基本的 vue 包保持同步,这样 vue-loader
就会生成兼容运行时的代码。这意味着你每次升级项目中的 vue 包时,也应该匹配升级 vue-template-compiler
。
webpack 配置
// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
module: {
rules: [
// ... 其它规则
{
test: /\.vue$/,
loader: 'vue-loader'
},
// 它会应用到普通的 `.js` 文件
// 以及 `.vue` 文件中的 `<script>` 块
{
test: /\.js$/,
loader: 'babel-loader'
},
// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
// 请确保引入这个插件!
//这个插件是必须的! 它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。
//例如,如果你有一条匹配 /\.js$/ 的规则,那么它会应用到 .vue 文件里的 <script> 块。
new VueLoaderPlugin()
]
}
3.处理资源路径
转换规则
当 Vue Loader 编译单文件组件中的<template> 块时,它也会将所有遇到的资源 URL 转换为 webpack 模块请求。
资源 URL 转换会遵循如下规则:
- 如果路径是绝对路径 (例如 /images/foo.png),会原样保留。
- 如果路径以 . 开头,将会被看作相对的模块依赖,并按照你的本地文件系统上的目录结构进行解析。
- 如果路径以 ~ 开头,其后的部分将会被看作模块依赖。这意味着你可以用该特性来引用一个 Node 依赖中的资源:
<img src="~some-npm-package/foo.png">
- 如果路径以 @ 开头,也会被看作模块依赖。如果你的 webpack 配置中给 @ 配置了 alias,这就很有用了。所有 vue-cli 创建的项目都默认配置了将 @ 指向 /src。
resolve: {
alias: {
'@': 'C:\\Users\\app\\src',
//因为运行时版本相比完整版体积要小大约 30%,所以应该尽可能使用这个版本。如果你仍然希望使用完整版,则需要在打包工具里配置一个别名:vue.runtime.js
vue$: 'vue/dist/vue.runtime.esm.js'// 用 webpack 1 时需用 'vue/dist/vue.common.js'
},
}
file-loader与url-loader
.png 这样的文件不是一个 JavaScript 模块,你需要配置 webpack 使用 file-loader 或者 url-loader 去合理地处理它们。通过 Vue CLI 创建的项目已经把这些预配置好了:
{
test: /\.(png|jpe?g|gif|webp)(\?.*)?$/,
use: [
/* config.module.rule('images').use('url-loader') */
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
},
file-loader 可以指定要复制和放置资源文件的位置,以及如何使用版本哈希命名以获得更好的缓存。此外,这意味着 你可以就近管理图片文件,可以使用相对路径而不用担心部署时 URL 的问题。使用正确的配置,webpack 将会在打包输出中自动重写文件路径为正确的 URL。
url-loader 允许你有条件地将文件转换为内联的 base-64 URL (当文件小于给定的阈值),这会减少小文件的 HTTP 请求数。如果文件大于该阈值,会自动的交给 file-loader 处理。
4.使用预处理器
Sass
npm install -D sass-loader node-sass
module.exports = {
module: {
rules: [
// ... 忽略其它规则
// 现在,除了能够 import 'style.scss',我们还可以在 Vue 组件中使用 SCSS
// 普通的 `.scss` 文件和 `*.vue` 文件中的
// `<style lang="scss">` 块都应用它
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
]
},
// 共享全局变量
// sass-loader 也支持一个 data 选项,这个选项允许你在所有被处理的文件之间共享常见的变量,而不需要显式地导入它们:
// webpack.config.js -> module.rules
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
// 你也可以从一个文件读取,例如 `variables.scss`
data: `$color: red;`
}
}
]
}
]
},
// 插件忽略
}
Less
npm install -D less less-loader
// webpack.config.js -> module.rules
{
test: /\.less$/,
use: [
'vue-style-loader',
'css-loader',
'less-loader'
]
}
Stylus
npm install -D stylus stylus-loader
// webpack.config.js -> module.rules
{
test: /\.styl(us)?$/,
use: [
'vue-style-loader',
'css-loader',
'stylus-loader'
]
}
5.Scoped CSS
当<style> 标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素。因为scoped会被转换为一个唯一的特性,用来表示当前组件。
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
<!-- 转换结果: -->
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>
<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>
子组件的根元素
使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。能用class还是用class,当 p { color: red } 是 scoped 时 (即与特性选择器组合使用时) 会慢很多倍。
深度作用选择器
如果你希望 scoped 样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用 >>>
操作符:
<style scoped>
.a >>> .b { /* ... */ }
</style>
<style scoped>
.a /deep/ .b { /* ... */ }
</style>
<style scoped>
.a/deep/ .b { /* ... */ }
</style>
<style scoped>
.a ::v-deep .b { /* ... */ }
</style>
上述代码将会编译成:
.a[data-v-f3f3eg9] .b { /* ... */ }
有些像 Sass 之类的预处理器无法正确解析 >>>
。这种情况下你可以使用 /deep/
或 ::v-deep
操作符取而代之——两者都是 >>>
的别名,同样可以正常工作。
动态生成的内容
通过 v-html 创建的 DOM 内容不受 scoped 样式影响,但是你仍然可以通过深度作用选择器来为他们设置样式。
6.CSS Modules
CSS Modules 是一个流行的,用于模块化和组合 CSS 的系统。vue-loader 提供了与 CSS Modules 的一流集成,可以作为模拟 scoped CSS 的替代方案。
首先,CSS Modules 必须通过向 css-loader 传入 modules: true 来开启:
// webpack.config.js
{
module: {
rules: [
//和预处理器配合使用
{
test: /\.scss$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: { modules: true }
},
'sass-loader'
]
}
// ... 其它规则省略
{//如果只有这个css规则的话,css只能是modules模式的,不能匹配普通的 `<style>` 或 `<style scoped>`,遇到普通的编译会出错
test: /\.css$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: true,
// 自定义生成的类名(这个属性在css-loader3.0.0版本以上已被删除)
//localIdentName: '[local]_[hash:base64:8]'
}
}
]
}
]
}
}
然后在你的 <style> 上添加 module 特性:
<style module>
.red {
color: red;
}
.bold {
font-weight: bold;
}
</style>
这个 module 特性指引 Vue Loader 作为名为 $style
的计算属性,向组件注入 CSS Modules 局部
(只能获取当前组件中的calss)对象。然后你就可以在模板中通过一个动态类绑定来使用它了:
<template>
<p :class="$style.red">
This should be red
</p>
<p :class="{ [$style.red]: isRed }">
Am I red?
</p>
<p :class="[$style.red, $style.bold]">
Red and bold
</p>
</template>
你也可以通过 JavaScript 访问到它:
export default {
created () {
console.log(this.$style.red)//{red:'xxxclass'}
// -> "red_1VyoJ-uZ"
// 一个基于文件名和类名生成的标识符
}
}
js中引入css:
//./style.css
.test {
color: green;
}
:global(.global) {
color: red;
}/* 全局class为global时颜色为red */
/* 显式的局部作用域语法:local(.className),等同于.className */
:local(.local) {
color: orchid;
}
import styles from "./style.css?line";//module.rules中css规则设置resourceQuery: /line/后,必须在css后面加上line参数才可以匹配到。
//显式的局部作用域语法:local(.className),等同于.className
document.getElementById('ss').innerHTML = `
<div class="${styles.test}">js中导入的css</div>
<div class="global">global样式</div>
<div class="${styles.local}">local样式</div>
`;
自定义的注入名称
在 .vue 中你可以定义不止一个<style>,为了避免被覆盖,你可以通过设置 module 属性来为它们定义注入后计算属性的名称。
<div :class="a.child1">a自定义module样式</div>
<div :class="b.child2">b自定义module样式</div>
<style module="a">
/* 注入标识符 a,可通过this.a在js中调用*/
</style>
<style module="b">
/* 注入标识符 b */
</style>
可选用法
如果你只想在某些 Vue 组件中使用 CSS Modules,你可以使用 oneOf 规则并在 resourceQuery 字符串中检查 module 字符串:
/js中只有import xxx from 'xxx.css?module’这个能匹配到。否则不生效
// webpack.config.js -> module.rules
{
test: /\.css$/,
oneOf: [
// 这里匹配 `<style module>`
{
resourceQuery: /module/,//js中只有import xxx from 'xxx.css?module'这个能匹配到。否则不生效
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
modules: true,
//The option importLoaders allows you to configure how many loaders before css-loader should be applied to @imported resources.
importLoaders: 1//https://github.com/webpack-contrib/css-loader#importloaders
//(这个属性在css-loader3.0.0版本以上已被删除)
//https://github.com/webpack-contrib/css-loader/commit/c4b7f715a81d06858778e54a4fb78258d48a1426
//localIdentName: '[local]_[hash:base64:5]'
}
},
'postcss-loader'//PostCSS 是一个允许使用 JS 插件转换样式的工具。 这些插件可以检查(lint)你的 CSS,支持 CSS Variables 和 Mixins, 编译尚未被浏览器广泛支持的先进的 CSS 语法,内联图片,以及其它很多优秀的功能。
//比如autoprefixer插件,加css前缀,必须使用browserslistrc来工作
//https://github.com/postcss/postcss/blob/master/README-cn.md
]
},
// 这里匹配普通的 `<style>` 或 `<style scoped>`
{
use: [
'vue-style-loader',
'css-loader',
'postcss-loader'
]
}
]
}
css modules例子:
<style module>
/* 显式的局部作用域语法:local(.localxxx),等同于.localxxx */
:local(.localxxx){
color:green
}
全局class,<div class="globalxxx">默认module,global样式</div>
:global(.globalxxx){
color:skyblue
}
.normal{
color: blueviolet;
}
</style>
<style module="aaa">
.className {
color: green;
background: red;
}
.child1 {
composes: className;
color:yellow;
}
</style>
<style module="bbb">
.child2 {
composes: test from "./style.css";/* test必须和style.css中类名一样 */
}
</style>
7.热重载
启用热重载后,当你修改 .vue 文件时,该组件的所有实例将在不刷新页面的情况下被替换
状态保留规则
- 当编辑一个组件的<template> 时,这个组件实例将就地重新渲染,并保留当前所有的私有状态。能够做到这一点是因为模板被编译成了新的无副作用的渲染函数。
- 当编辑一个组件的 <script> 时,这个组件实例将就地销毁并重新创建。(应用中其它组件的状态将会被保留) 是因为 <script> 可能包含带有副作用的生命周期钩子,所以将重新渲染替换为重新加载是必须的,这样做可以确保组件行为的一致性。这也意味着,如果你的组件带有全局副作用,则整个页面将会被重新加载。
- <style> 会通过 vue-style-loader 自行热重载,所以它不会影响应用的状态
webpack中 devServer: {hot:false }的hot选项是一级热重载开关,vueloader中hotReload选项是二级热重载开关。
hot 和 hotOnly 的区别是在某些模块不支持热更新的情况下,前者会自动刷新页面,后者不会刷新页面,而是在控制台输出热更新失败
hot:true,hostReload:true//热重载
hot:true,hostReload:false//刷新整个页面,不是热重载
hot:false,hostReload:true//刷新整个页面,不是热重载
hot:false,hostReload:false//刷新整个页面,不是热重载
你可以设置 hotReload: false 选项来显式地关闭热重载:
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
hotReload: false // 关闭热重载
}
}
]
}
8.函数式组件
要声明一个应该编译为函数式组件的模板,请将 functional
特性添加到模板块中。这样做以后就可以省略 <script> 块中的 functional 选项。
模板中的表达式会在函数式渲染上下文中求值。这意味着在模板中,prop 需要以 props.xxx 的形式访问:
<template functional>
<div>{{ props.foo }}</div>
</template>
你可以在 parent 上访问 Vue.prototype 全局定义的属性:
<template functional>
<div>{{ parent.$someProperty }}</div>
</template>
9.自定义块
在 .vue 文件中,你可以自定义语言块。应用于一个自定义块的 loader 是基于这个块的 lang 特性
、块的标签名
以及你的 webpack 配置进行匹配的。
如果指定了一个 lang 特性(lang=“xxx”),则这个自定义块将会作为一个带有该 lang 扩展名
的文件进行匹配。
<xxxx lang="langxxx">
This is langModule。
</xxxx>
<div lang="langxxx">
This is langModule div。
</div>
你也可以使用 resourceQuery 来为一个没有 lang 的自定义块匹配一条规则。例如为了匹配自定义块<foo>:
{
module: {
rules: [
{
resourceQuery: /blockType=foo/,
loader: 'loader-to-use'
}
]
}
}
如果找到了一个自定义块的匹配规则,它将会被处理,否则该自定义块会被默默忽略。
此外,如果这个自定义块被所有匹配的 loader 处理之后导出一个函数作为最终结果,则这个 *.vue 文件的组件会作为一个参数被这个函数调用。
这里有一个向组件内注入 自定义块的示例,且它是在运行时可用的。
为了注入自定义块的内容,我们将会撰写一个自定义 loader:
module.exports = function (source, map) {
this.callback(
null,
`export default function (Component) {
Component.options.__docs = ${
JSON.stringify(source)
}
}`,
map
)
}
现在我们将会配置 webpack 来使用为 <docs> 自定义块撰写的自定义 loader。
// wepback.config.js
module.exports = {
module: {
rules: [
{
//匹配vueloader自定义模块/ComponentA.vue中标签名为docs的块
resourceQuery: /blockType=docs/,
loader: require.resolve('./docs-loader.js')
},
{
//匹配vueloader自定义模块/ComponentA.vue中lang 特性为langxxx的块
test: /\.langxxx$/,
loader: require.resolve('./docs-loader.js')
},
]
}
}
现在我们可以在运行时访问被导入组件的 <docs> 块内容了。
<!-- ComponentB.vue -->
<template>
<div>Hello</div>
</template>
<docs>
This is the documentation for component B.
</docs>
<xxxx lang="langxxx">
This is langModule。
</xxxx>
<div lang="langxxx">
This is langModule div。
</div>
<!-- ComponentA.vue -->
<template>
<div>
<ComponentB/>
<p>{{ docs }}</p><!-- This is langModule div。 -->
</div>
</template>
<script>
import ComponentB from './ComponentB.vue';
export default {
components: { ComponentB },
data () {
return {
docs: ComponentB.__docs
}
}
}
</script>
10.CSS 提取
请只在生产环境
下使用 CSS 提取,这将便于你在开发环境下进行热重载
。
// webpack 4
// npm install -D mini-css-extract-plugin
// webpack.config.js
var MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
// 其它选项...
module: {
rules: [
// ... 忽略其它规则
{
test: /\.css$/,
use: [
process.env.NODE_ENV !== 'production'
? 'vue-style-loader'
: MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
// ... 忽略 vue-loader 插件
new MiniCssExtractPlugin({
filename: 'style.css'
})
]
}
// webpack 3
// npm install -D extract-text-webpack-plugin
// webpack.config.js
var ExtractTextPlugin = require("extract-text-webpack-plugin")
module.exports = {
// 其它选项...
module: {
rules: [
// ...其它规则忽略
{
test: /\.css$/,
loader: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: 'vue-style-loader'
})
}
]
},
plugins: [
// ...vue-loader 插件忽略
new ExtractTextPlugin("style.css")
]
}
11.代码校验 (Linting)
官方的 eslint-plugin-vue 同时支持在 Vue 单文件组件的模板和脚本部分的代码校验(就是eslint对vue语法的规则扩展,有了这个可以校验vue语法规则)。
请确认在你的 ESLint 配置文件中使用该插件要导入的配置:
// .eslintrc.js
module.exports = {
extends: [
"plugin:vue/essential"
]
}
命令行中立即校验修复文件:
eslint --fix --ext .js,.vue src1 --ext src2,
eslint-loder
请确保它是作为一个 pre-loader 运用的:
// webpack.config.js
module.exports = {
// ... 其它选项
module: {
rules: [
{
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /node_modules/
}
]
}
}
stylelint
yarn add -D stylelint stylelint-webpack-plugin postcss-styl
postcss-styl@0.3.0 Expected version “^8.10.0 || >=11.10.1”
stylelint 支持在 Vue 单文件组件的样式部分的代码校验。
接下来从命令行运行:
stylelint MyComponent.vue
另一个选项是使用 stylelint-webpack-plugin:
npm install -D stylelint-webpack-plugin
请确保它是作为一个插件运用的:
//package.json
"stylelint": {
"rules": {
"block-no-empty": null,
"color-no-invalid-hex": true,
"comment-empty-line-before": [
"always",
{
"ignore": [
"stylelint-commands",
"after-comment"
]
}
],
"declaration-colon-space-after": "always",
"indentation": [
"tab",
{
"except": [
"value"
]
}
],
"max-empty-lines": 2,
"rule-empty-line-before": [
"always",
{
"except": [
"first-nested"
],
"ignore": [
"after-comment"
]
}
],
"unit-whitelist": [
"em",
"rem",
"%",
"s"
]
}
}
// webpack.config.js
const StyleLintPlugin = require('stylelint-webpack-plugin');
module.exports = {
// ... 其它选项
plugins: [
new StyleLintPlugin({
files: ['**/*.{vue,htm,html,css,sss,less,scss,sass}'],
})
]
}
12.单文件组件规范
语言块
模板
每个 .vue 文件最多包含一个 <template> 块。
内容将被提取并传递给 vue-template-compiler 为字符串,预处理为 JavaScript 渲染函数,并最终注入到从 <script> 导出的组件中。
脚本
每个 .vue 文件最多包含一个 <script> 块。
这个脚本会作为一个 ES Module 来执行。
它的默认导出应该是一个 Vue.js 的组件选项对象。也可以导出由 Vue.extend() 创建的扩展对象,但是普通对象是更好的选择。
任何匹配 .js 文件 (或通过它的 lang 特性指定的扩展名) 的 webpack 规则都将会运用到这个 <script> 块的内容中。
样式
默认匹配:/.css$/。
一个 .vue 文件可以包含多个 <style> 标签。
<style> 标签可以有 scoped 或者 module 属性 (查看 scoped CSS和 CSS Modules) 以帮助你将样式封装到当前组件。具有不同封装模式的多个 <style> 标签可以在同一个组件中混合使用。
任何匹配 .css 文件 (或通过它的 lang 特性指定的扩展名) 的 webpack 规则都将会运用到这个 <style> 块的内容中。
Src 导入
如果喜欢把 .vue 文件分隔到多个文件中,你可以通过 src 属性导入外部文件:
<template src="./template.html"></template>
<style src="./style.css"></style>
<script src="./script.js"></script>
需要注意的是 src 导入遵循和 webpack 模块请求相同的路径解析规则,这意味着:
相对路径需要以 ./ 开始
你可以从 NPM 依赖中导入资源:
<!-- import a file from the installed "todomvc-app-css" npm package -->
<style src="todomvc-app-css/index.css">
在自定义块上同样支持 src 导入,例如:
<unit-test src="./unit-test.js">
</unit-test>
13.其他
样式注入
现在客户端的样式注入会在最前面注入所有的样式以确保开发模式和提取模式下行为的一致性。
注意它们注入的顺序是不能保证的,所以你撰写的 CSS 应该避免依赖插入的顺序。
从依赖中导入单文件组件
exclude: /node_modules/ 在运用于 .js 文件的 JS 转译规则 (例如 babel-loader) 中是蛮常见的。如果你导入一个 node_modules 内的 Vue 单文件组件,它的 <script> 部分在转译时将会被排除在外。
为了确保 JS 的转译应用到 node_modules 的 Vue 单文件组件,你需要通过使用一个排除函数将它们加入白名单:
{
test: /\.js$/,
loader: 'babel-loader',
exclude: file => (
/node_modules/.test(file) &&
!/\.vue\.js/.test(file)
)
}
14.vueloader选项
//webpack
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
xxx
}
},
prettify
类型:boolean
默认值:true
在开发环境下,我们默认使用 prettier 格式化编译后的模板渲染代码,以方便调试。
exposeFilename
类型:boolean
默认值:false
在非生产环境下,vue-loader 会为组件注入一个 __file 属性以提升调试体验。如果一个组件没有 name 属性,Vue 会通过 __file 字段进行推断,并用于控制台警告中的展示。
这个属性在生产环境构建时会被去掉。但如果你在开发一个组件库并且烦于为每个组件设置 name,你可能还会想使用它。这时可以把这个选项打开。
cacheDirectory / cacheIdentifier
类型:string
默认值:undefined
当这两个选项同时被设置时,开启基于文件系统的模板编译缓存 (需要在工程里安装 cache-loader)。
注意
在内部,vue-loader 和 cache-loader 之间的交互使用了 loader 的内联 import 语法,!
将会被认为是不同 loaders 之间的分隔符,所以请确保你的 cacheDirectory 路径中不包含 !。
hotReload
类型:boolean
默认值:在开发环境下是 true,在生产环境下或 webpack 配置中有 target: ‘node’ 的时候是 false。
允许的值:false (true 会强制热重载,即便是生产环境或 target: ‘node’ 时)
是否使用 webpack 的模块热替换在浏览器中应用变更而不重载整个页面。 用这个选项 (值设为 false) 在开发环境下关闭热重载特性。
productionMode
类型:boolean
默认值:process.env.NODE_ENV === ‘production’
强制指定为生产环境,即禁止 loader 注入只在开发环境有效的代码 (例如 hot-reload 相关的代码)。
compiler
类型:VueTemplateCompiler
默认值:require(‘vue-template-compiler’)
覆写用来编译单文件组件中<template> 块的默认编译器。
transformAssetUrls
类型:{ [tag: string]: string | Array<string> }
默认值:
{
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href']
}
在模板编译过程中,编译器可以将某些特性转换为 require 调用,例如 src 中的 URL。因此这些目标资源可以被 webpack 处理。例如 <img src="./foo.png"> 会找到你文件系统中的 ./foo.png 并将其作为一个依赖包含在你的包里。