其餘章節請看: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]{}
在遞歸組件中當心使用後代選擇器!
其餘章節請看: