其余章节请看:javascript
vue 快速入门 系列css
经过前面“webpack 系列”的学习,咱们知道如何用 webpack 实现一个不成熟的脚手架,好比提供开发环境和生成环境,开发环境提供本地服务器,有热模块替换,能使用 sass、es6等开发项目。html
实际工做中咱们可能会使用声明式框架 vue 或 react 来开发项目,而它们都提供了相应的脚手架。在学习 vue-cli(vue官方的脚手架)以前,咱们先来玩一下 vue loader。前端
Tip:本篇也能够称之为“vue loader 官网”笔记。经过本篇文章,咱们能学会编写一个简单的,用于单文件组件开发的脚手架;以及对单文件组件规范有一个初步的认识和理解。vue
注:本文不少配置都参考笔者的另外一篇文章webpack 快速入门 系列 —— 实战一,好比babel、postcss、图片、css提取等配置。java
Vue Loader 是一个 webpack 的 loader,它容许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。node
<template> <div class="example">{{ msg }}</div> </template> <script> export default { data () { return { msg: 'Hello world!' } } } </script> <style> .example { color: red; } </style>
Tip: 更详细的介绍请看下面的“Vue 单文件组件 (SFC) 规范”章节。react
Vue Loader 还提供了不少酷炫的特性:webpack
<style>
的部分使用 Sass 和在 <template>
的部分使用 Pug;<style>
和 <template>
中引用的资源看成模块依赖来处理;简而言之,webpack 和 Vue Loader 的结合为你提供了一个现代、灵活且极其强大的前端工做流,来帮助撰写 Vue.js 应用。es6
Tip: 上面这些特性,下文都会详细介绍。
直接参考"实战一->准备本篇的环境"一节,搭建好 test-vue-loader 项目。
附上项目:
test-vue-loader - src // 项目源码 - a.css - b.js - c.js - index.html // 页面模板 - index.js // 入口 - package.json // 存放了项目依赖的包 - webpack.config.js // webpack配置文件
如今咱们要把 App.vue 这个单文件组件跑起来:
// src/App.vue <template> <div class="example">{{ msg }}</div> </template> <script> export default { data () { return { msg: 'Hello world!' } } } </script> <style> .example { color: red; } </style>
请看操做:
首先将 vue-loader 和 vue-template-compiler 一块儿安装。
// vue 包固然也须要 > npm install -D vue@2 vue-loader@15 vue-template-compiler@2
每一个 vue 包的新版本发布时,一个相应版本的 vue-template-compiler 也会随之发布。每次升级项目中的 vue 包时,也应该匹配升级 vue-template-compiler。
接着修改配置文件,核心代码以下:
const { VueLoaderPlugin } = require('vue-loader') module.exports = { module: { rules: [ // ... 其它规则 { test: /\.vue$/, loader: 'vue-loader' } ] }, plugins: [ // 请确保引入这个插件! new VueLoaderPlugin() ] }
在将 src/index.js 改成:
//引入Vue import Vue from 'vue'; //引入组件 import App from './App.vue'; new Vue({ el: "body", template: '<App/>', components: {App} });
经过 npm run dev
启动,浏览器控制台报错,信息以下:
[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build. (found in <Root>) // 翻译 您正在使用 Vue 的仅运行时构建,其中模板编译器不可用。 要么将模板预编译为渲染函数,要么使用包含编译器的构建。
告诉咱们,正使用“只包含运行时版”,能够将模板编译为渲染函数,或者使用包含编译器的构建。
Tip:vue 有不一样的版本,例如:
在“模块(module)”一文中介绍了 require() 方法加载第三方模块的规则,因此 import Vue from 'vue';
就会去加载 test-vue-loader\node_modules\vue\package.json
中 main 指向的文件("main": "dist/vue.runtime.common.js"
),确实是运行时版。
能够经过如下两种方式修改 index.js 来解决这个问题。
// inidex.js - import Vue from 'vue'; // 前面会匹配是否核心包、第三方包、路径查找(./ 或 ../ 或 /),最后读取到项目目录下 node_modules 包里的包 // vue.esm.js ES Module (基于构建工具使用),而且是完整版 + import Vue from 'vue/dist/vue.esm.js' ...
Tip:这种方式在浏览器中会有以下警告:
[Vue warn]: Do not mount Vue to <html> or <body> - mount to normal elements instead.
因此将 el 中的 body 改成 #app 这种形式便可。
// index.js import Vue from 'vue'; import App from './App.vue'; new Vue({ el: "body", render: h => h(App) });
浏览器页面显示红色文字”Hello world!“,单文件组件解析成功。
当 Vue Loader 编译单文件组件中的 <template>
块时,它也会将全部遇到的资源 URL 转换为 webpack 模块请求。
让 App.vue 引入一张图片:
<template> <div class="example"> {{ msg }} <!-- 增长图片 --> <img src="./6.68kb.png"></img> </div> </template>
终端报错:
... You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. // 翻译 您可能须要一个合适的加载器来处理此文件类型,目前没有配置加载器来处理此文件。
下载包,并修改配置:
> npm i -D url-loader@4 file-loader@6
// webpack.config.js -> module.rules { test: /\.(png|jpg|gif)$/i, use: [ { loader: 'url-loader', options: { // 调整的比 6.68 要小,这样图片就不会打包成 base64 limit: 1024*6, }, }, ], },
再次运行,浏览器仍是看不到图片,查看源码:
<div class="example"> Hello world! <img src="[object Module]"> </div>
图片的 src 有问题,这是由于 url-loader 默认采用 ES 模块语法,而 Vue 生成的是 CommonJS 模块语法,即 require('./6.68kb.png')
,解决方法是让两者采用相同的模板语法,下面将 url-loader 的 es-module 关闭:
// webpack.config.js module: { rules: [ ... { test: /\.(png|jpg|gif)$/i, use: [ { loader: 'url-loader', options: { limit: 1024*6, // + esModule: false, }, }, ], }, ] },
再次运行,图片正常显示。控制台查看:
<div class="example"> Hello world! <img src="26bd867dd65e26dbc77d1e151ffd36e0.png"> </div>
修改 App.vue,将其改成绝对路径
<img src="/6.68kb.png"></img>
浏览器页面中图片没出来,检查元素:
<div class="example"> Hello world! <img src="/6.68kb.png"> </div>
经过运行 npm run build
发现 dist 目录中也没有生成 6.68kb.png。因而咱们知道使用绝对路径,不只会原样保留,也不会将该资源打包出去。
将图片 6.68kb.png 拷贝到一个模块 node_modules/vue 中
修改图片引用
<img src="~vue/6.68kb.png"></img>
图片正常显示
将路径改成以 @ 开头,终端报错:
// 以 @ 开头 <img src="@vue/6.68kb.png"></img> // 终端报错 Module not found: Error: Can't resolve '@vue/6.68kb.png' in '....\test-vue-loader\src'
@ 是指向 /src 吗?仍是报错:
<img src="@/6.68kb.png"></img> // 终端报错 Module not found: Error: Can't resolve '@/6.68kb.png' in...
给 @ 配置 alias,图片正常显示。请看代码:
<img src="@/6.68kb.png"></img> // 给 @ 配置 alias module.exports = { ... resolve: { alias: { '@': path.resolve(__dirname, 'src/'), }, }, }
在 webpack 中,全部的预处理器须要匹配对应的 loader。Vue Loader 容许你使用其它 webpack loader 处理 Vue 组件的某一部分。它会根据 lang 特性以及你 webpack 配置中的规则自动推断出要使用的 loader。
给 App.vue 增长 sass 代码:
// App.vue 尾部增长以下sass代码 <style lang="scss"> $size: 3em; .example { font-size: $size; } </style>
为了能让 sass/scss 生效,须要安装依赖包:
> npm i -D sass-loader@10 node-sass@6
修改配置文件:
// webpack.config.js -> module.rules // 普通的 `.scss` 文件和 `*.vue` 文件中的 // `<style lang="scss">` 块都应用它 { test: /\.scss$/, use: [ 'vue-style-loader', 'css-loader', 'sass-loader' ] }
重启服务,你会发现页面中的 ”hello world“ 字号变大,sass 编译成功。
Tip: vue-style-loader 是一个基于 style-loader 的 fork。 与 style-loader 相似,您能够将其连接在 css-loader 以后,以将 CSS 做为样式标签动态注入文档。 可是,因为它做为依赖项包含在 vue-loader 中并默认使用,所以在大多数状况下,您不须要本身配置此加载器,即无需下载 vue-style-loader 便可使用。
sass-loader 默认处理不基于缩进的 scss 语法。
将 sass 改成缩进语法,终端会报错:
// 缩进语法 <style lang="sass"> $size: 3em .example font-size: $size; </style> // 终端报错 SassError: Invalid CSS after "$size: 3em": expected expression (e.g. 1px, bold), was ".example "...
注:须要将 lang 从 scss 改成 sass,配合下面的 rule 工做。
为了使用基于缩进的 sass 语法,你须要向这个 loader 传递选项:
// webpack.config.js -> module.rules { test: /\.sass$/, use: [ 'vue-style-loader', 'css-loader', { loader: 'sass-loader', options: { // sass-loader version >= 8 sassOptions: { indentedSyntax: true } } } ] },
重启服务器,缩进语法生效了。
sass-loader 也支持一个 prependData 选项,这个选项容许你在全部被处理的文件之间共享常见的变量,而不须要显式地导入它们。请看示例:
// webpack.config.js -> module.rules { test: /\.sass$/, use: [ ... { loader: 'sass-loader', options: { ..., additionalData: `$size: 3em;`, } } ] },
App.vue 中直接使用 $size,而无需定义:
... <style lang="sass"> .example font-size: $size; </style>
若直接在 App.vue 中增长以下 less 的样式,会报错:
// 给 App.vue 增长 less 语法 <style lang="less"> @size: 2em; .example { font-size: @size } </style>
// 终端报错: ERROR in ./src/App.vue?vue&type=style&index=2&lang=less&.. Module parse failed: Unexpected character '@' (29:0)...
安装依赖,并增长 rule,重启服务便可生效。
> npm i -D less@4 less-loader@7
// webpack.config.js -> module.rules { test: /\.less$/, use: [ 'vue-style-loader', 'css-loader', 'less-loader' ] }
若直接在 App.vue 中增长以下 stylus 的样式,会报错:
// 给 App.vue 增长 stylus 语法 <style lang="stylus"> /* stylus 语法 */ $size = 3em .example font-size: $size </style>
// 终端报错: ERROR in ./src/App.vue?vue&type=style&index=3&lang=stylus&..
安装依赖,并增长 rule,重启服务便可生效。
> npm i -D stylus@0 stylus-loader@4
// webpack.config.js -> module.rules { test: /\.styl(us)?$/, use: [ 'vue-style-loader', 'css-loader', 'stylus-loader' ] }
tip:Vue Loader v15 再也不默认应用 PostCSS 变换。你须要经过 postcss-loader 使用 PostCSS。
咱们的 vue loader 是 15.9.7,知足该条件。
postcss-loader 能够和上述其它预处理器结合使用。下面咱们就给 less 预处理器添加 postcss。
修改 App.vue,给 less 中增长明天的 css 语法:
// lch 是明天的CSS <style lang="less"> @size: 2em; .example { color: lch(100 100 100); font-size: @size } </style>
浏览器查看样式,发现 color: lch(100 100 100)
没生效。
安装依赖包,并修改配置文件:
> npm i -D postcss-loader@4 postcss-preset-env@6
// webpack.config.js // + const postcssLoader = { loader: 'postcss-loader', options: { // postcss 只是个平台,具体功能须要使用插件 postcssOptions:{ plugins:[ [ "postcss-preset-env", { browsers: 'ie >= 8, chrome > 10', }, ], ] } } } module: { rules: [ { test: /\.less$/, use: [ 'vue-style-loader', 'css-loader', // + postcssLoader, 'less-loader' ] },
从新启动服务器,”Hello World!“ 显示黄色。lch 也编译成了 color: rgb(255, 255, 0)
首先编写箭头函数,若是打包后能转为普通函数,则说明 babel 配置成功。
给 App.vue 增长箭头函数:
<script> export default { ... }; // 箭头函数 const sum = (a, b) => (a + b); console.log(sum(1, 10)); </script>
浏览器的控制台输出 11,但在浏览器中的源中查看 mian.js,发现箭头函数没有转为普通函数。
const sum = (a, b) => (a + b);\r\nconsole.log(sum(1, 10));\r\n\n\n/
安装依赖,并修改配置:
> npm i -D babel-loader@8 @babel/preset-env@7
// webpack.config.js -> module.rules { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env'] ] } } }
从新启动服务器,箭头函数就变成普通函数:
var sum = function sum(a, b) {\n return a + b;\n};\n\nconsole.log(sum(1, 10));
exclude: /node_modules/
在应用于 .js 文件的 JS 转译规则 (例如 babel-loader) 中是蛮常见的。鉴于 v15 中的推导变化,若是你导入一个 node_modules 内的 Vue 单文件组件,它的 <script>
部分在转译时将会被排除在外。
咱们将 src/App.vue 拷贝一份到 node_modules/vue 目录中,并修改 index.js 中 App.vue 的引入方式:
- import App from './App.vue'; + import App from 'vue/App.vue';
在浏览器中的源中查看 mian.js,发现箭头函数没有转为普通函数:
const sum = (a, b) => (a + b);\r\nconsole.log(sum(11, 10));
为了确保 js 的转译应用到 node_modules 的 Vue 单文件组件,你须要经过使用一个排除函数将它们加入白名单:
{ test: /\.js$/, exclude: file => ( /node_modules/.test(file) && !/\.vue\.js/.test(file) ), ... }
重启服务便可生效
注:进行下面测试以前,别忘了还原 App.vue 的引入
import App from './App.vue'
给 App.vue 写入 ts 代码,终端报错:
// 修改 App.vue 的 script <script lang='ts'> export default { ... } ... /* typescript */ class Greeter<T> { greeting: T; constructor(message: T) { this.greeting = message; } greet() { return this.greeting; } } let greeter = new Greeter<string>("Hello, world"); console.log(greeter.greet()) </script>
// 终端报错 ... You may need an additional loader to handle the result of these loaders. | | /* typescript */ > class Greeter<T> { | greeting: T; | constructor(message: T) {
安装依赖包,并修改配置:
> npm i -D typescript@4 ts-loader@7
// webpack.config.js module.exports = { resolve: { // 将 `.ts` 添加为一个可解析的扩展名。 extensions: ['.ts', '.js'] }, module: { rules: [ // ... 忽略其它规则 { test: /\.ts$/, loader: 'ts-loader', options: { appendTsSuffixTo: [/\.vue$/] } } ] }, ... }
重启服务,终端报错:
[tsl] ERROR TS18002: The 'files' list in config file 'tsconfig.json' is empty.
新建 test-vue-loader/tsconfig.json,内容以下:
{ "compilerOptions": { "sourceMap": true } }
重启服务,终端报错信息变为:
TS18003: No inputs were found in config file 'tsconfig.json'. Specified 'include' paths were '["**/*"]' and 'exclude' paths were '[]'.
能够给 tsconfig.json 增长 allowJs:
{ "compilerOptions": { "allowJs": true, "sourceMap": true } }
重启服务器,终端没有抛出错误,浏览器控制台成功输出 ”hello, TypeScript 解析成功。
Pug 是一个高性能模板引擎,深受 Haml 影响,使用 JavaScript 实现,适用于 Node.js 和浏览器;
Pug 是一种用于编写 html 的干净、对空格敏感的语法
在 App.vue 中使用 pug,从新打包,停住了:
<template> <div class="example"> ... </div> </template> <!-- 多个 template,会以最后一个 template 为准--> <template lang="pug"> div h1 I am pug! </template>
// 打包 test-vue-loader> npm run build > test-vue-loader@1.0.0 build > webpack 不动了...
猜想多是没有配置 pug 致使的。因而安装依赖,并修改配置:
> npm i -D pug@3 pug-plain-loader@1
// webpack.config.js -> module.rules { test: /\.pug$/, loader: 'pug-plain-loader' }
从新启动服务器,浏览器页面显示 I am pug!
,pug 解析成功。
// 浏览器查看源码 <div> <h1>I am pug!</h1> </div>
Tip: 为了减小影响,方便学习和测试,能够将 App.vue 的代码所有注释,就像这样<!-- App.vue 的全部代码 -->
当 <style>
标签有 scoped 属性时,它的 CSS 只做用于当前组件中的元素,它有一些注意事项,但不须要任何 polyfill。
修改 App.vue 内容,给 style 增长 scoped:
<template> <div class="example">hi</div> </template> <style scoped> .example { color: red; } </style>
经过浏览器检查:
.example[data-v-7ba5bd90] { color: red; } <div data-v-7ba5bd90="" class="example">hi</div>
Tip:文档说它经过使用 PostCSS 来实现转换,但目前个人 postcss 只结合 less 使用,这里使用的明显是 css,因此猜测 postCss 难道内置了!
<style scoped> .example { color: red; } </style> <style> .example { font-size: 2em; } </style>
转换结果:
<style> .example[data-v-7ba5bd90] { color: red; } </style> <style> .example { font-size: 2em; }
使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响。这样设计是为了让父组件能够从布局的角度出发,调整其子组件根元素的样式。—— 官网
什么意思?咱们作个测试就明白了
新建一个子组件:
// Box.vue <template> <div class='m-box'> children <p>m-box p1</p> </div> </template>
在 App.vue 中引用子组件:
<template> <div class="s-c1"> <p>hi</p> <Box/> </div> </template> <script> import Box from './Box.vue' export default { data () { return { msg: 'Hello world!' } }, components:{ Box } } </script> <style scoped> .s-c1 { color: red; } </style>
页面中三行文字全是红色。
hi children m-box p1
子组件明明没有写样式,并且父组件的样式也写在 scope 中,为何子组件的文字也变成红色?
浏览器查看代码:
<style> .s-c1[data-v-7ba5bd90] { color: red; } </style> <div data-v-7ba5bd90="" class="s-c1"> <p data-v-7ba5bd90="">hi</p> <div data-v-1461803c="" data-v-7ba5bd90="" class="m-box"> children <p data-v-1461803c="">m-box p1</p> </div> </div>
原来咱们写的代码转成这种形式,子组件的文字确实应该是红色。
而上面提到:”不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响“
指的应该是子组件的根元素上既有子组件的标记,也有父组件的标记,即同时有 data-v-1461803c=""
和 data-v-7ba5bd90=""
将 App.vue 的 style 改为下面代码,在页面中会看得更清晰:
<style scoped> .s-c1 { color: red; margin:10px;padding:10px; } div{ border: 1px solid blue; } </style>
不只父组件有边框,子组件的的根(div)也会有蓝色边框。
若是你但愿 scoped 样式中的一个选择器可以做用得“更深”,例如影响子组件,你可使用 >>>
操做符:
只调整父组件中 p 元素的字号,能够这么写:
<style scoped> ... div p{font-size:2em;} </style>
转换成:
div p[data-v-7ba5bd90]{font-size:2em;}
若是也但愿调整子组件中 p 元素的字号:
div >>> p{font-size:2em;}
转换成:
div[data-v-7ba5bd90] p{font-size:2em;}
若是但愿只做用于 div 的孩子节点:
div >>> > p{font-size:2em;}
转换成:
div[data-v-7ba5bd90] > p{font-size:2em;}
注:>>>>
不会生效
有些像 Sass 之类的预处理器没法正确解析 >>>。这种状况下你可使用 /deep/ 或 ::v-deep 操做符取而代之——二者都是 >>> 的别名,一样能够正常工做。
经过 v-html 建立的 DOM 内容不受 scoped 样式影响,可是你仍然能够经过深度做用选择器来为他们设置样式。
咱们作个测试
咱们给 App.vue 和 Box.vue 都增长 v-html,代码以下:
// App.vue <template> <div class="s-c1"> <p>hi</p> <!-- + --> <span v-html='aHtml'></span> <Box/> </div> </template> <script> ... export default { data () { return { // + aHtml: '<p>i am aHtml</p>' } }, } </script> <style scoped> ... div >>> p{font-size:2em;} </style>
// Box.vue <template> <div class='m-box'> children <span v-html='bHtml'></span> <p>m-box p1</p> </div> </template> <script> export default { data () { return { bHtml: '<p>i am bHtml</p>' } } } </script>
i am aHtml
和 i am bHtml
字号都是 2em
浏览器查看转换后的代码:
div[data-v-7ba5bd90] p{font-size:2em;}
<div data-v-7ba5bd90="" class="s-c1" style="margin: 10px; padding: 10px;"> <p data-v-7ba5bd90="">hi</p> <span data-v-7ba5bd90=""> <p>i am aHtml</p> </span> <div data-v-1461803c="" data-v-7ba5bd90="" class="m-box">children2 <span data-v-1461803c=""> <p>i am bHtml</p> </span> <p data-v-1461803c="">m-box p1</p></div> </div>
将深度做用选择器删除后测试:
div p{font-size:2em;}
i am aHtml
和 i am bHtml
字号都不在是 2em。
转换后的代码是:
div p[data-v-7ba5bd90]{font-size:2em;}
<div data-v-7ba5bd90="" class="s-c1"> <p data-v-7ba5bd90="">hi</p> <span data-v-7ba5bd90=""> <p>i am aHtml</p> </span> <div data-v-7ba5bd90="" class="m-box"> children <span> <p>i am bHtml</p> </span> <p>m-box p1</p> </div> </div>
v-html生成的元素都不会有特殊的标记,好比这里的 data-v-7ba5bd90
。
至此,咱们就理解了开头的话。
Scoped 样式不能代替 class。考虑到浏览器渲染各类 CSS 选择器的方式,当 p { color: red } 是 scoped 时 (即与特性选择器组合使用时) 会慢不少倍。若是你使用 class 或者 id 取而代之,好比 .example { color: red },性能影响就会消除。
div{} .div{}
转换成:
div[data-v-7ba5bd90]{} .div[data-v-7ba5bd90]{}
在递归组件中当心使用后代选择器!
其余章节请看: