一文讓你徹底弄懂 “vue-style-loader” 跟 “style-loader” 區(qū)別

簡(jiǎn)介

用過(guò) vue-cli 腳手架搭建 vue 項(xiàng)目都知道,vue-cli 中內(nèi)置了 vue-style-loader 去加載樣式模塊府阀,最后通過(guò) <style> 標(biāo)簽把樣式加載到頁(yè)面失乾,但是 style-loader 同樣可以達(dá)到同樣的效果,那么 vue 官方為啥還封裝一個(gè) vue-style-loader 庫(kù)呢犹撒?那么它們到底有啥區(qū)別横腿?平時(shí)項(xiàng)目中又該如何選擇呢颓屑?

下面先看一下它們各自官網(wǎng)的簡(jiǎn)介。

開始

為了更好的去分析這兩個(gè)庫(kù)耿焊,我們簡(jiǎn)單的搭建一個(gè) vue 項(xiàng)目揪惦。

其實(shí)如果對(duì) webpack 很熟悉的話,從 0 開始搭建一個(gè) vue 項(xiàng)目就很簡(jiǎn)單了罗侯,所以強(qiáng)烈推薦大家去看一下我上一篇 webpack 的文章器腋,來(lái)和 webpack 談場(chǎng)戀愛(ài)吧

首先創(chuàng)建一個(gè)目錄叫 style-loader-demo钩杰,然后初始化 npm

mkdir style-loader-demo && cd style-loader-demo && npm init

接下來(lái)我們需要安裝 webpack 相關(guān)的依賴:

安裝 webpack

webpack 核心庫(kù)纫塌。

在工程目錄 style-loader-demo 執(zhí)行以下命令安裝 webpack:

npm install -D webpack --registry https://registry.npm.taobao.org

安裝 webpack-cli

webpack 指令庫(kù)。

在工程目錄 style-loader-demo 執(zhí)行以下命令:

npm install -D webpack-cli --registry https://registry.npm.taobao.org

安裝 webpack-dev-server

webpack 開發(fā)者服務(wù)框架榜苫。

在工程目錄 style-loader-demo 執(zhí)行以下命令:

npm install -D webpack-dev-server --registry https://registry.npm.taobao.org

安裝 webpack-chain

webpack 配置工具护戳。

在工程目錄 style-loader-demo 執(zhí)行以下命令:

npm install -D webpack-chain --registry https://registry.npm.taobao.org

創(chuàng)建 webpack 配置

在工程目錄 style-loader-demo 下創(chuàng)建一個(gè) webpack.config.js 文件:

touch webpack.config.js

然后對(duì) webpack.config.js 進(jìn)行配置,用 webpack-chain 導(dǎo)入一個(gè) webpack 配置:

const config = new (require('webpack-chain'))();

module.exports = config.toConfig();

為了開發(fā)方便垂睬,我們?cè)?package.json 中聲明兩個(gè)腳本 builddev

{
  "name": "style-loader-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "rimraf dist && webpack --mode=production",
    "dev": "webpack-dev-server --mode=development --progress"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "css-loader": "^5.0.0",
    "html-webpack-plugin": "^4.5.0",
    "style-loader": "^2.0.0",
    "vue-style-loader": "^4.1.2",
    "webpack": "^4.44.2",
    "webpack-chain": "^6.5.1",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "vue": "^2.6.12",
    "vue-loader": "^15.9.3",
    "vue-template-compiler": "^2.6.12"
  }
}

入口與出口

我們首先在工程目錄 style-loader-demo 下創(chuàng)建一個(gè) src 目錄,然后在 src 目錄下創(chuàng)建一個(gè) main.s 文件:

mkdir src && cd src && touch main.js && cd ..

然后我們找到 webpack.config.js 文件抗悍,對(duì) webpack 的入口和出口進(jìn)行配置:

const path = require('path');
const config = new (require('webpack-chain'))();
config
    .context(path.resolve(__dirname, '.')) // webpack 上下文目錄為項(xiàng)目根目錄
        .entry('app') // 入口文件名稱為 app
        .add('./src/main.js') // 入口文件為 ./src/main.ts
        .end()
    .output
        .path(path.join(__dirname, './dist')) // webpack 輸出的目錄為根目錄的 dist 目錄
        .filename('[name].[hash:8].js') // 打包出來(lái)的 bundle 名稱為 "[name].[hash:8].js"
        .publicPath('/') // publicpath 配置為 "/"
        .end()

安裝 vue

vue 核心 API驹饺。

npm install vue --registry https://registry.npm.taobao.org

安裝 vue-loader

.vue 文件加載器。

npm install vue-loader --registry https://registry.npm.taobao.org

安裝 vue-template-compiler

.vue 文件模版解析器缴渊。

npm install vue-template-compiler --registry https://registry.npm.taobao.org

安裝 html-webpack-plugin

npm install -D html-webpack-plugin --registry https://registry.npm.taobao.org

接下來(lái)我們?cè)诠こ棠夸?sy_webpack-wedding 底下創(chuàng)建一個(gè) public 目錄赏壹,然后在 public 目錄下創(chuàng)建一個(gè) index.html 文件作為我們 app 的入口頁(yè)面:

mkdir public && touch public/index.html

然后將以下內(nèi)容寫入 public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
  </head>
  <body>
    <noscript>your browser should support javascript!</noscript>
    <div id="app"></div>
    <!-- html-webpack-plugin 將自動(dòng)引入入口文件 -->
  </body>
</html>

安裝 css-loader

css 模塊加載器。

npm install -D css-loader --registry https://registry.npm.taobao.org

安裝 vue-style-loader

npm install -D vue-style-loader --registry https://registry.npm.taobao.org

安裝 style-loader

npm install -D style-loader --registry https://registry.npm.taobao.org

配置 webpack.config.js

webpack 配置全部?jī)?nèi)容:

const path = require('path');
const config = new (require('webpack-chain'))();
config
    .context(path.resolve(__dirname, '.')) // webpack 上下文目錄為項(xiàng)目根目錄
    .entry('app') // 入口文件名稱為 app
        .add('./src/main.js') // 入口文件為 ./src/main.ts
        .end()
    .output
        .path(path.join(__dirname, './dist')) // webpack 輸出的目錄為根目錄的 dist 目錄
        .filename('[name].[hash:8].js') // 打包出來(lái)的 bundle 名稱為 "[name].[hash:8].js"
        .publicPath('/') // publicpath 配置為 "/"
        .end()
    .resolve
        .extensions
            .add('.js')
            .add('.vue') // 配置以 .js 等結(jié)尾的文件當(dāng)模塊使用的時(shí)候都可以省略后綴
            .end()
        .end()
    .module
        .rule('vue') // vue-loader 相關(guān)配置
            .test(/\.vue$/) // 匹配 .vue 文件
            .use('vue-loader')
                .loader('vue-loader')
                .end()
            .end()
        .rule('css') // css-loader 相關(guān)配置
            .test(/\.css$/)
            .use('vue-style-loader')
                .loader('vue-style-loader')
                .end()
            .use('css-loader')
                .loader('css-loader')
                .options({
                    esModule: false
                })
                .end()
            .end()
        .end()
    .plugin('vue-loader-plugin') // vue-loader 必須要添加 vue-loader-plugin
        .use(require('vue-loader').VueLoaderPlugin, [])
        .end()
    .plugin('html') // 添加 html-webpack-plugin 插件
        .use(require('html-webpack-plugin'), [
            {
                template: path.resolve(__dirname, './public/index.html'), // 指定模版文件
                chunks: ['app'], // 指定需要加載的 chunk
                inject: 'body', // 指定 script 腳本注入的位置為 body
            },
        ])
        .end()
    .devServer
        .host('0.0.0.0') // 服務(wù)器外部可訪問(wèn)
        .disableHostCheck(true) // 關(guān)閉白名單校驗(yàn)
        .contentBase(path.resolve(__dirname, './public')) // 設(shè)置一個(gè) express 靜態(tài)目錄
        .historyApiFallback({
            disableDotRule: true, // 禁止在鏈接中使用 "." 符號(hào)
            rewrites: [
                { from: /^\/$/, to: '/index.html' }, // 將所有的 404 響應(yīng)重定向到 index.html 頁(yè)面
            ],
        })
        .port(8080) // 當(dāng)前端口號(hào)
        .hot(true) // 打開頁(yè)面熱載功能
        .sockPort('location') // 設(shè)置成平臺(tái)自己的端口
        .open(true);
module.exports = config.toConfig();

測(cè)試

我們?cè)?src 目錄下創(chuàng)建一個(gè) app.vue 文件:

touch src/app.vue

然后將以下內(nèi)容寫入其中:

<template>
    <div class="app">{{ msg }}</div>
</template>

<script>
export default {
    name: "app",
    data(){
        return {
            msg: "hello"
        }
    }
}
</script>
<style scoped>
    .app {
        color: red;
    }
</style>

很簡(jiǎn)單衔沼,就是一個(gè)簡(jiǎn)單的 vue 文件蝌借。

然后在 main.js 中引入 app.vue 文件:

import Vue from 'vue';
import App from './app.vue';
new Vue({
    el: '#app',
    render: (h) => h(App),
});

ok昔瞧!一切準(zhǔn)備完畢后,我們直接在工程目錄運(yùn)行 npm run dev 命令:

npm run dev

運(yùn)行完畢后瀏覽器打開:

1.1.png

跟不上的童鞋可以直接下載源碼:https://gitee.com/vv_bug/style-loader-demo

vue-style-loader

官網(wǎng)地址:https://github.com/vuejs/vue-style-loader

This is a fork based on style-loader. Similar to style-loader, you can chain it after css-loader to dynamically inject CSS into the document as style tags. However, since this is included as a dependency and used by default in vue-loader, in most cases you don't need to configure this loader yourself.

意思大概是:“基于 style-loader fork 過(guò)來(lái)的菩佑,跟 style-loader 類似自晰。”

官網(wǎng)解釋的還是比較敷衍的稍坯,我們結(jié)合 Demo 繼續(xù)往下看酬荞。

看一下現(xiàn)在的設(shè)置,我們找到 webpack.config.js

const path = require('path');
const config = new (require('webpack-chain'))();
config
    .context(path.resolve(__dirname, '.')) // webpack 上下文目錄為項(xiàng)目根目錄
    .entry('app') // 入口文件名稱為 app
        .add('./src/main.js') // 入口文件為 ./src/main.ts
        .end()
    .output
        .path(path.join(__dirname, './dist')) // webpack 輸出的目錄為根目錄的 dist 目錄
        .filename('[name].[hash:8].js') // 打包出來(lái)的 bundle 名稱為 "[name].[hash:8].js"
        .publicPath('/') // publicpath 配置為 "/"
        .end()
    .resolve
        .extensions
            .add('.js')
            .add('.vue') // 配置以 .js 等結(jié)尾的文件當(dāng)模塊使用的時(shí)候都可以省略后綴
            .end()
        .end()
    .module
        .rule('vue') // vue-loader 相關(guān)配置
            .test(/\.vue$/) // 匹配 .vue 文件
            .use('vue-loader')
                .loader('vue-loader')
                .end()
            .end()
        .rule('css') // css-loader 相關(guān)配置
            .test(/\.css$/)
            .use('vue-style-loader')
                .loader('vue-style-loader')
                .end()
            .use('css-loader')
                .loader('css-loader')
                .options({
                    esModule: false
                })
                .end()
            .end()
        .end()
    .plugin('vue-loader-plugin') // vue-loader 必須要添加 vue-loader-plugin
        .use(require('vue-loader').VueLoaderPlugin, [])
        .end()
    .plugin('html') // 添加 html-webpack-plugin 插件
        .use(require('html-webpack-plugin'), [
            {
                template: path.resolve(__dirname, './public/index.html'), // 指定模版文件
                chunks: ['app'], // 指定需要加載的 chunk
                inject: 'body', // 指定 script 腳本注入的位置為 body
            },
        ])
        .end()
    .devServer
        .host('0.0.0.0') // 服務(wù)器外部可訪問(wèn)
        .disableHostCheck(true) // 關(guān)閉白名單校驗(yàn)
        .contentBase(path.resolve(__dirname, './public')) // 設(shè)置一個(gè) express 靜態(tài)目錄
        .historyApiFallback({
            disableDotRule: true, // 禁止在鏈接中使用 "." 符號(hào)
            rewrites: [
                { from: /^\/$/, to: '/index.html' }, // 將所有的 404 響應(yīng)重定向到 index.html 頁(yè)面
            ],
        })
        .port(8080) // 當(dāng)前端口號(hào)
        .hot(true) // 打開頁(yè)面熱載功能
        .sockPort('location') // 設(shè)置成平臺(tái)自己的端口
        .open(true);
module.exports = config.toConfig();

再看一下現(xiàn)在的樣式瞧哟,我們找到 src/app.vue 文件:

<style scoped>
    .app {
        color: red;
    }
</style>

上面的樣式到底是怎樣起作用的呢混巧?

首先經(jīng)過(guò) vue-loader 處理一遍變成了這樣:

.app[data-v-5ef48958] {
    color: red;
}

可以看到,我們樣式經(jīng)過(guò) vue-loader 處理后加上了一個(gè) “data-v-5ef48958”勤揩,這是為什么呢咧党?因?yàn)槲覀冊(cè)?style 標(biāo)簽上加了 scoped 標(biāo)記:

<style scoped>

所以 vue-loader 會(huì)給所有的樣式都加上一個(gè) scoped 的屬性。

vue-loader 處理過(guò)后就到了 css-loader陨亡,經(jīng)過(guò) css-loader 處理后:

// Imports
var ___CSS_LOADER_API_IMPORT___ = require("../node_modules/css-loader/dist/runtime/api.js");
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
// Module
___CSS_LOADER_EXPORT___.push([module.id, "\n.app[data-v-5ef48958] {\n    color: red;\n}\n", ""]);
// Exports
module.exports = ___CSS_LOADER_EXPORT___;

可以看到傍衡,css-loader 處理過(guò)后會(huì)把樣式都變成 module 形式,然后直接導(dǎo)出這個(gè)模塊数苫,模塊中包含了 css 的源碼跟模塊的 id聪舒。

css-loader 處理過(guò)后會(huì)被 vue-style-loader 引用:

// style-loader: Adds some css to the DOM by adding a <style> tag

// load the styles
var content = require("!!../node_modules/css-loader/dist/cjs.js??ref--1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./app.vue?vue&type=style&index=0&id=5ef48958&scoped=true&lang=css&");
if(typeof content === 'string') content = [[module.id, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
var add = require("!../node_modules/vue-style-loader/lib/addStylesClient.js").default
var update = add("d929f8d0", content, false, {});
// Hot Module Replacement
if(module.hot) {
 // When the styles change, update the <style> tags
 if(!content.locals) {
   module.hot.accept("!!../node_modules/css-loader/dist/cjs.js??ref--1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./app.vue?vue&type=style&index=0&id=5ef48958&scoped=true&lang=css&", function() {
     var newContent = require("!!../node_modules/css-loader/dist/cjs.js??ref--1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./app.vue?vue&type=style&index=0&id=5ef48958&scoped=true&lang=css&");
     if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
     update(newContent);
   });
 }
 // When the module is disposed, remove the <style> tags
 module.hot.dispose(function() { update(); });
}

上面代碼中的 content 就是 css-loader 處理過(guò)后的模塊。

我們看一下 update 方法的聲明:

...
var add = require("!../node_modules/vue-style-loader/lib/addStylesClient.js").default
var update = add("d929f8d0", content, false, {});
...

可以看到虐急,update 方法是通過(guò) add 方法構(gòu)建的箱残,add 方法又是通過(guò) !../node_modules/vue-style-loader/lib/addStylesClient.js 文件導(dǎo)入的,所以我們找到 !../node_modules/vue-style-loader/lib/addStylesClient.js 文件止吁,我們直接定位到 xx/style-loader-demo/node_modules/vue-style-loader/lib/addStylesClient.js 文件:

...
var ssrIdKey = 'data-vue-ssr-id'
function createStyleElement () {
  var styleElement = document.createElement('style')
  styleElement.type = 'text/css'
  head.appendChild(styleElement)
  return styleElement
}

function addStyle (obj /* StyleObjectPart */) {
  var update, remove
  var styleElement = document.querySelector('style[' + ssrIdKey + '~="' + obj.id + '"]')

  if (styleElement) {
    if (isProduction) {
      // has SSR styles and in production mode.
      // simply do nothing.
...

可以看到被辑,其實(shí)就是創(chuàng)建了一個(gè) style 標(biāo)簽,然后直接插入到了 head 標(biāo)簽中敬惦,但是 vue-style-loader 除了插入 style 外盼理,還做了一些服務(wù)端渲染的支持,所以如果你的 vue 項(xiàng)目是需要支持服務(wù)端渲染的時(shí)候俄删,就需要用到 vue-style-loader了宏怔,原理是不是很簡(jiǎn)單呢?

style-loader

官網(wǎng)地址:https://github.com/webpack-contrib/style-loader

Inject CSS into the DOM.

哈哈畴椰,無(wú)語(yǔ)了臊诊,style-loader 甚至都懶得解釋了。

ok斜脂!既然兩位大佬官網(wǎng)都懶得解釋了抓艳,那我們就只能自己去結(jié)合 Demo 看源碼了。

我們先用一下 style-loader帚戳,我們修改一下 webpack.config.js 文件:

const path = require('path');
const config = new (require('webpack-chain'))();
config
    .context(path.resolve(__dirname, '.')) // webpack 上下文目錄為項(xiàng)目根目錄
    .entry('app') // 入口文件名稱為 app
        .add('./src/main.js') // 入口文件為 ./src/main.ts
        .end()
    .output
        .path(path.join(__dirname, './dist')) // webpack 輸出的目錄為根目錄的 dist 目錄
        .filename('[name].[hash:8].js') // 打包出來(lái)的 bundle 名稱為 "[name].[hash:8].js"
        .publicPath('/') // publicpath 配置為 "/"
        .end()
    .resolve
        .extensions
            .add('.js')
            .add('.vue') // 配置以 .js 等結(jié)尾的文件當(dāng)模塊使用的時(shí)候都可以省略后綴
            .end()
        .end()
    .module
        .rule('vue') // vue-loader 相關(guān)配置
            .test(/\.vue$/) // 匹配 .vue 文件
            .use('vue-loader')
                .loader('vue-loader')
                .end()
            .end()
        .rule('css') // css-loader 相關(guān)配置
            .test(/\.css$/)
            .use('style-loader')
                .loader('style-loader')
                .end()
            .use('css-loader')
                .loader('css-loader')
                .options({
                    esModule: false
                })
                .end()
            .end()
        .end()
    .plugin('vue-loader-plugin') // vue-loader 必須要添加 vue-loader-plugin
        .use(require('vue-loader').VueLoaderPlugin, [])
        .end()
    .plugin('html') // 添加 html-webpack-plugin 插件
        .use(require('html-webpack-plugin'), [
            {
                template: path.resolve(__dirname, './public/index.html'), // 指定模版文件
                chunks: ['app'], // 指定需要加載的 chunk
                inject: 'body', // 指定 script 腳本注入的位置為 body
            },
        ])
        .end()
    .devServer
        .host('0.0.0.0') // 服務(wù)器外部可訪問(wèn)
        .disableHostCheck(true) // 關(guān)閉白名單校驗(yàn)
        .contentBase(path.resolve(__dirname, './public')) // 設(shè)置一個(gè) express 靜態(tài)目錄
        .historyApiFallback({
            disableDotRule: true, // 禁止在鏈接中使用 "." 符號(hào)
            rewrites: [
                { from: /^\/$/, to: '/index.html' }, // 將所有的 404 響應(yīng)重定向到 index.html 頁(yè)面
            ],
        })
        .port(8080) // 當(dāng)前端口號(hào)
        .hot(true) // 打開頁(yè)面熱載功能
        .sockPort('location') // 設(shè)置成平臺(tái)自己的端口
        .open(true);
module.exports = config.toConfig();

可以看到玷或,我們把 vue-style-loader 換成了 style-loader:

...
.rule('css') // css-loader 相關(guān)配置
            .test(/\.css$/)
            .use('style-loader')
                .loader('style-loader')
                .end()
            .use('css-loader')
                .loader('css-loader')
                .options({
                    esModule: false
                })
                .end()
...

然后再次運(yùn)行一下 npm run dev 命令看效果:

npm run dev
1.1.png

可以看到儡首,跟 vue-style-loader 的效果是一樣的缺厉,這是為什么呢歼狼?

跟前面 vue-style-loader 分析方式一樣,我們先看一下現(xiàn)在的樣式磨澡,我們找到 src/app.vue 文件:

<style scoped>
    .app {
        color: red;
    }
</style>

上面的樣式到底是怎樣起作用的呢约谈?

首先經(jīng)過(guò) vue-loader 處理一遍變成了這樣:

.app[data-v-5ef48958] {
    color: red;
}

可以看到笔宿,我們樣式經(jīng)過(guò) vue-loader 處理后加上了一個(gè) “data-v-5ef48958”,這是為什么呢棱诱?因?yàn)槲覀冊(cè)?style 標(biāo)簽上加了 scoped 標(biāo)記:

<style scoped>

所以 vue-loader 會(huì)給所有的樣式都加上一個(gè) scoped 的屬性泼橘。

vue-loader 處理過(guò)后就到了 css-loader,經(jīng)過(guò) css-loader 處理后:

// Imports
var ___CSS_LOADER_API_IMPORT___ = require("../node_modules/css-loader/dist/runtime/api.js");
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(function(i){return i[1]});
// Module
___CSS_LOADER_EXPORT___.push([module.id, "\n.app[data-v-5ef48958] {\n    color: red;\n}\n", ""]);
// Exports
module.exports = ___CSS_LOADER_EXPORT___;

可以看到迈勋,css-loader 處理過(guò)后會(huì)把樣式都變成 module 形式炬灭,然后直接導(dǎo)出這個(gè)模塊,模塊中包含了 css 的源碼跟模塊的 id靡菇。

到這里其實(shí)都跟前面 vue-style-loader 方式一樣重归。

css-loader 處理過(guò)后會(huì)被 style-loader 引用,代碼變成了這樣:

import api from "!../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js";
            import content from "!!../node_modules/css-loader/dist/cjs.js??ref--1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./app.vue?vue&type=style&index=0&id=5ef48958&scoped=true&lang=css&";

var options = {};

options.insert = "head";
options.singleton = false;

var update = api(content, options);


if (module.hot) {
  if (!content.locals || module.hot.invalidate) {
    var isEqualLocals = function isEqualLocals(a, b, isNamedExport) {
  if (!a && b || a && !b) {
    return false;
  }

  var p;

  for (p in a) {
    if (isNamedExport && p === 'default') {
      // eslint-disable-next-line no-continue
      continue;
    }

    if (a[p] !== b[p]) {
      return false;
    }
  }

  for (p in b) {
    if (isNamedExport && p === 'default') {
      // eslint-disable-next-line no-continue
      continue;
    }

    if (!a[p]) {
      return false;
    }
  }

  return true;
};
    var oldLocals = content.locals;

    module.hot.accept(
      "!!../node_modules/css-loader/dist/cjs.js??ref--1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./app.vue?vue&type=style&index=0&id=5ef48958&scoped=true&lang=css&",
      function () {
        if (!isEqualLocals(oldLocals, content.locals, undefined)) {
                module.hot.invalidate();

                return;
              }

              oldLocals = content.locals;

              update(content);
      }
    )
  }

  module.hot.dispose(function() {
    update();
  });
}

export default content.locals || {};

可以看到厦凤,上面的代碼:

import api from "!../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js";
            import content from "!!../node_modules/css-loader/dist/cjs.js??ref--1-1!../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../node_modules/vue-loader/lib/index.js??vue-loader-options!./app.vue?vue&type=style&index=0&id=5ef48958&scoped=true&lang=css&";
...

content 就是 css-loader 處理過(guò)后的 css樣式模塊鼻吮,包含了 css 源碼跟 moduleid,那么 這里的 api 是啥呢较鼓?我們找到 xxx/style-loader-demo/node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 文件:

...
function insertStyleElement(options) {
  var style = document.createElement('style');
  var attributes = options.attributes || {};

  if (typeof attributes.nonce === 'undefined') {
    var nonce = typeof __webpack_nonce__ !== 'undefined' ? __webpack_nonce__ : null;

    if (nonce) {
      attributes.nonce = nonce;
    }
  }

  Object.keys(attributes).forEach(function (key) {
    style.setAttribute(key, attributes[key]);
  });

  if (typeof options.insert === 'function') {
    options.insert(style);
  } else {
    var target = getTarget(options.insert || 'head');

    if (!target) {
      throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");
    }

    target.appendChild(style);
  }

  return style;
}

function removeStyleElement(style) {
  // istanbul ignore if
  if (style.parentNode === null) {
    return false;
  }

  style.parentNode.removeChild(style);
}
/* istanbul ignore next  */


var replaceText = function replaceText() {
  var textStore = [];
  return function replace(index, replacement) {
    textStore[index] = replacement;
    return textStore.filter(Boolean).join('\n');
  };
}();

function applyToSingletonTag(style, index, remove, obj) {
  var css = remove ? '' : obj.media ? "@media ".concat(obj.media, " {").concat(obj.css, "}") : obj.css; // For old IE

  /* istanbul ignore if  */

  if (style.styleSheet) {
    style.styleSheet.cssText = replaceText(index, css);
  } else {
    var cssNode = document.createTextNode(css);
    var childNodes = style.childNodes;

    if (childNodes[index]) {
      style.removeChild(childNodes[index]);
    }

    if (childNodes.length) {
      style.insertBefore(cssNode, childNodes[index]);
    } else {
      style.appendChild(cssNode);
    }
  }
}

function applyToTag(style, options, obj) {
  var css = obj.css;
  var media = obj.media;
  var sourceMap = obj.sourceMap;

  if (media) {
    style.setAttribute('media', media);
  } else {
    style.removeAttribute('media');
  }

  if (sourceMap && typeof btoa !== 'undefined') {
    css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */");
  } // For old IE

  /* istanbul ignore if  */


  if (style.styleSheet) {
    style.styleSheet.cssText = css;
  } else {
    while (style.firstChild) {
      style.removeChild(style.firstChild);
    }

    style.appendChild(document.createTextNode(css));
  }
}

var singleton = null;
var singletonCounter = 0;

function addStyle(obj, options) {
  var style;
  var update;
  var remove;

  if (options.singleton) {
    var styleIndex = singletonCounter++;
    style = singleton || (singleton = insertStyleElement(options));
    update = applyToSingletonTag.bind(null, style, styleIndex, false);
    remove = applyToSingletonTag.bind(null, style, styleIndex, true);
  } else {
    style = insertStyleElement(options);
    update = applyToTag.bind(null, style, options);

    remove = function remove() {
      removeStyleElement(style);
    };
  }

  update(obj);
  return function updateStyle(newObj) {
    if (newObj) {
      if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) {
        return;
      }

      update(obj = newObj);
    } else {
      remove();
    }
  };
}

module.exports = function (list, options) {
  options = options || {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
  // tags it will allow on a page

  if (!options.singleton && typeof options.singleton !== 'boolean') {
    options.singleton = isOldIE();
  }

  list = list || [];
  var lastIdentifiers = modulesToDom(list, options);
  return function update(newList) {
    newList = newList || [];

    if (Object.prototype.toString.call(newList) !== '[object Array]') {
      return;
    }

    for (var i = 0; i < lastIdentifiers.length; i++) {
      var identifier = lastIdentifiers[i];
      var index = getIndexByIdentifier(identifier);
      stylesInDom[index].references--;
    }

    var newLastIdentifiers = modulesToDom(newList, options);

    for (var _i = 0; _i < lastIdentifiers.length; _i++) {
      var _identifier = lastIdentifiers[_i];

      var _index = getIndexByIdentifier(_identifier);

      if (stylesInDom[_index].references === 0) {
        stylesInDom[_index].updater();

        stylesInDom.splice(_index, 1);
      }
    }

    lastIdentifiers = newLastIdentifiers;
  };
};

可以看到椎木,其實(shí)跟 vue-style-loader 一樣,也是往 head 標(biāo)簽中注入一個(gè) style 標(biāo)簽:

1.2.png

但是 style-loader 處理支持普通的注入 style 外博烂,還支持以下模式:

  • linkTag:生成一個(gè) link 標(biāo)簽香椎,把 css-loader 處理過(guò)后的值賦給 link 標(biāo)簽的 href 屬性。
  • lazyStyleTag & lazySingletonStyleTag:懶注入 style 標(biāo)簽禽篱。
  • styleTag & singletonStyleTag:默認(rèn)方式畜伐,直接注入 style 標(biāo)簽。

ok躺率!我們拿一種方式操作一下玛界,我們用一下 lazySingletonStyleTag

首先我們創(chuàng)建一個(gè) loaders 目錄悼吱,然后在 loaders 目錄下創(chuàng)建一個(gè) customer-style-loader.js 文件:

mkdir loaders && touch loaders/customer-style-loader.js

然后將以下代碼寫入 loaders/customer-style-loader.js 文件:

module.exports=function (source) {
    return `
        ${source}
        setTimeout(()=>{
            alert("來(lái)來(lái)來(lái)脚仔,show style!")
            exported && exported.use && exported.use();
        },5000);
    `;
}

可以看到舆绎,我們延遲了 5s 去顯示我們的樣式,然后我們找到 webpack.config.js 引入我們的 customer-style-loader.js们颜,并且把 style-loader 的 injectType 設(shè)置成 lazySingletonStyleTag

const path = require('path');
const config = new (require('webpack-chain'))();
config
    .context(path.resolve(__dirname, '.')) // webpack 上下文目錄為項(xiàng)目根目錄
        .entry('app') // 入口文件名稱為 app
        .add('./src/main.js') // 入口文件為 ./src/main.ts
        .end()
    .output
        .path(path.join(__dirname, './dist')) // webpack 輸出的目錄為根目錄的 dist 目錄
        .filename('[name].[hash:8].js') // 打包出來(lái)的 bundle 名稱為 "[name].[hash:8].js"
        .publicPath('/') // publicpath 配置為 "/"
        .end()
    .resolve
        .extensions
            .add('.js')
            .add('.vue') // 配置以 .js 等結(jié)尾的文件當(dāng)模塊使用的時(shí)候都可以省略后綴
            .end()
        .end()
    .module
        .rule('vue') // vue-loader 相關(guān)配置
            .test(/\.vue$/) // 匹配 .vue 文件
            .use('vue-loader')
                .loader('vue-loader')
                .end()
            .end()
        .rule('css') // css-loader 相關(guān)配置
            .test(/\.css$/)
            .use('customer-style-loader')
                .loader(path.resolve("./loaders/customer-style-loader.js"))
                .end()
            .use('vue-style-loader')
                .loader('style-loader')
                .options({
                    injectType: "lazySingletonStyleTag"
                })
                .end()
            .use('css-loader')
                .loader('css-loader')
                .options({
                    esModule: false
                })
                .end()
            .end()
        .end()
    .plugin('vue-loader-plugin') // vue-loader 必須要添加 vue-loader-plugin
        .use(require('vue-loader').VueLoaderPlugin, [])
        .end()
    .plugin('html') // 添加 html-webpack-plugin 插件
        .use(require('html-webpack-plugin'), [
            {
                template: path.resolve(__dirname, './public/index.html'), // 指定模版文件
                chunks: ['app'], // 指定需要加載的 chunk
                inject: 'body', // 指定 script 腳本注入的位置為 body
            },
        ])
        .end()
    .devServer
        .host('0.0.0.0') // 服務(wù)器外部可訪問(wèn)
        .disableHostCheck(true) // 關(guān)閉白名單校驗(yàn)
        .contentBase(path.resolve(__dirname, './public')) // 設(shè)置一個(gè) express 靜態(tài)目錄
        .historyApiFallback({
            disableDotRule: true, // 禁止在鏈接中使用 "." 符號(hào)
            rewrites: [
                { from: /^\/$/, to: '/index.html' }, // 將所有的 404 響應(yīng)重定向到 index.html 頁(yè)面
            ],
        })
        .port(8080) // 當(dāng)前端口號(hào)
        .hot(true) // 打開頁(yè)面熱載功能
        .sockPort('location') // 設(shè)置成平臺(tái)自己的端口
        .open(true);
module.exports = config.toConfig();

然后運(yùn)行 npm run dev 命令打開瀏覽器看效果:

npm run dev
1.3.gif

可以看到吕朵,當(dāng)我們刷新頁(yè)面后猎醇,“hello” 一開始是沒(méi)有樣式的,然后過(guò)后 5s 后彈出 alert 點(diǎn)擊 “確定” 后才有樣式的努溃。

ok硫嘶!style-loader 還有一些其它的功能,我就不演示了梧税,小伙伴自己去研究哦沦疾!

總結(jié)

vue-style-loader 跟 style-loader 基本用法跟功能是一樣的,都是往 dom 里面插入一個(gè) style 標(biāo)簽去讓樣式生效的第队,但是 vue-style-loader 支持 vue 中的 ssr(服務(wù)端渲染)哮塞,所以如果需要支持服務(wù)端渲染的 vue 項(xiàng)目,就需要用到 vue-style-loader了凳谦,如果一般的 vue 項(xiàng)目的話忆畅,推薦使用 style-loader,畢竟 style-loader 支持的功能還是豐富些尸执,比如可以懶注入家凯、可以指定位置插入標(biāo)簽等等。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末如失,一起剝皮案震驚了整個(gè)濱河市绊诲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌褪贵,老刑警劉巖掂之,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異竭鞍,居然都是意外死亡板惑,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門偎快,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)冯乘,“玉大人,你說(shuō)我怎么就攤上這事晒夹●陕” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵丐怯,是天一觀的道長(zhǎng)喷好。 經(jīng)常有香客問(wèn)我,道長(zhǎng)读跷,這世上最難降的妖魔是什么梗搅? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上无切,老公的妹妹穿的比我還像新娘荡短。我一直安慰自己,他們只是感情好哆键,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布掘托。 她就那樣靜靜地躺著,像睡著了一般籍嘹。 火紅的嫁衣襯著肌膚如雪闪盔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天辱士,我揣著相機(jī)與錄音泪掀,去河邊找鬼。 笑死识补,一個(gè)胖子當(dāng)著我的面吹牛族淮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凭涂,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼祝辣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了切油?” 一聲冷哼從身側(cè)響起蝙斜,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澎胡,沒(méi)想到半個(gè)月后孕荠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡攻谁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年稚伍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戚宦。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡个曙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出受楼,到底是詐尸還是另有隱情垦搬,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布艳汽,位于F島的核電站猴贰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏河狐。R本人自食惡果不足惜米绕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一瑟捣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧义郑,春花似錦蝶柿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雏赦。三九已至劫笙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間星岗,已是汗流浹背填大。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俏橘,地道東北人允华。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寥掐,于是被迫代替她去往敵國(guó)和親靴寂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359