設置全局css/less/sass樣式and優化與style-resources-loader的理解

css全局樣式設置

問題描述:一個項目往往是有非常多的界面組成,當我們在編寫代碼的時候,這些界面就會有很多重複使用的/公共的樣式重疊。這時候,可以將這些樣式抽離出來放在一個共享文件裏,將樣式作用到各個界面。

使用方法:
1. 最直接方便的方法是在assets裏面寫好公用樣式文件,然後在main.js引入:

import './assets/css/common.less';

2. 上述方法雖然能夠使樣式文件全局作用,但是對於一些我們定義的變量、函數,則不會在全局起作用,也就是說當我們在A.less文件中要使用B.less文件中的@color變量,或者在C.vue文件中使用B.less文件中的@color變量,那麼需要先在當前文件引入B.less

import '~assets/css/B.less';

有N多個文件需要用到@color變量,就需要在N多個文件中引入B.less

這裏有個優化:import (reference) '~assets/css/B.less' 加上(reference)能夠減少css生成體積。————導入的樣式不會添加到編譯輸出,除非該樣式被引用。

如果我們不想每次都引入B.less,那麼可以藉助style-resources-loader來將css變量注入全局:

types.forEach(type => addStyleResource(config.module.rule('less').oneOf(type)));
...
function addStyleResource(rule) {
    rule.use('style-resource')
        .loader('style-resources-loader')
        .options({
            patterns: [
                path.resolve(__dirname, './src/assets/css/B.less')
            ],
        });
}

這裏有一點需要注意,並不是所有的全局css文件都放到這裏來,style-resources-loader這個loader的主要作用使將變量、函數、mixin注入到全局,如果普通的css文件也放到這裏來會導致樣式文件在index.html文件的head中重複引用N遍。

所以我們的B.less文件中只需要放變量、函數一些內容,其他普通的全局樣式文件在main.js文件引入就好了。

上述使用loader優化css全局

出現的問題:樣式文件在head中被多次重複引用

問題出處:style-resources-loader主要作用是將預編譯樣式的變量、函數、mixin注入到全局,而普通css文件則直接在main.js中引入,不需要style-resources-loader引入。

思考:爲什麼style-resources-loader會出現重複引用樣式文件的情況:
打開被重複引用的樣式文件,發現文件裏把所有的在style-resources-loader中引入的css代碼都合併到了一起,也就是說,所有被設爲全局樣式的css代碼作爲一個整體(一個文件)被加載(引入)了N次。

原理:

webpack中loader:
loader:是webpack用來預處理模塊的,在一個模塊被引入之前,會預先使用loader處理模塊的內容,在你打包的時候對某些內容需要loader來處理一下,比如css模塊;默認webpack只會處理js代碼,所以當我們想要去打包其他內容時,就需要相應的loader去處理某些內容。

style-resources-loader:
找到style-resources-loader的入口文件index.ts:

import loader from './loader';

export * from './schema';
export * from './types';

export default loader;

這裏主要引入了loader文件,找到loader.ts文件:

import {errorMessage, isFunction, loadResources} from './utils';

import {Loader, LoaderCallback} from '.';

/* eslint-disable no-invalid-this */
const loader: Loader = function(source) {
    this.cacheable && this.cacheable();

    const callback = this.async();

    if (!isFunction<LoaderCallback>(callback)) {
        throw new Error(errorMessage.syncCompilation);
    }

    /* istanbul ignore if: not possible to test */
    if (typeof source !== 'string') {
        throw new Error(errorMessage.impossible);
    }

    /* eslint-disable-next-line @typescript-eslint/no-floating-promises */
    loadResources(this, source, callback);
};
/* eslint-enable no-invalid-this */

export default loader;

loader.ts從utils文件中引入了errorMessage(錯誤消息提示), isFunction(函數判斷), loadResources(資源加載)工具。返回的loader是一個函數,它判斷了loader是不是一個函數,接收的參數source是不是字符串.loader('style-resources-loader'),然後它又調用了loadResources方法並傳入當前上下文和source,callback。

我們找到load-resources.ts文件:

import {LoaderContext, LoaderCallback} from '..';

import {normalizeOptions, getResources, injectResources} from '.';

export const loadResources = async (ctx: LoaderContext, source: string, callback: LoaderCallback) => {
    try {
        const options = normalizeOptions(ctx);

        const resources = await getResources(ctx, options);

        const content = await injectResources(options, source, resources);

        return callback(null, content);
    } catch (err) {
        return callback(err);
    }
};

LoaderContext, LoaderCallback是一些ts校驗規則,我們可以先忽略。然後這個文件又引入了normalizeOptions, getResources, injectResources這些工具,並且最終期望是返回content,也就是我們的loader可以一直鏈式添加。

normalizeOptions這個函數主要利用validateOptions這個函數然後返回一些我們設置的一些options。

return {
   patterns: normalizePatterns(patterns),  // 表示要注入的資源的路徑
   injector: normalizeInjector(injector),  // 可選功能,可精確控制資源注入。支持'prepend'和'append'參數,這意味着加載程序會將所有資源分別放在源文件的前面或後面。默認爲'prepend',在內部實現爲注入功能。
   globOptions,  // glob化
   resolveUrl,   // 是否應解析@import或中的相對路徑@require
};

對應我們的使用:

options: {
    patterns: path.resolve(__dirname, 'path/to/less/variables/*.less'),
    injector: 'append'
}

getResources是個異步方法,主要是遍歷格式化文件並返回文件路徑。

const resources = await Promise.all(
    files.map(async file => {
        const content = await util.promisify(fs.readFile)(file, 'utf8');
        const resource: StyleResource = {file, content};

        return resolveUrl ? resolveImportUrl(ctx, resource) : resource;
    }),
);

return resources;

injectResources接收options, source, resources參數,並注入到loader中。

主要是這個處理路徑的文件,重寫了我們傳入的樣式文件路徑。當我們在多個文件中引用B.less中的變量時,這個變量樣式會被注入到每個引用到該變量的文件中。如果我們將基礎樣式也用loader載入,基礎樣式會被全部注入到引用了變量的文件裏。導致樣式重複渲染。

import path from 'path';

import {LoaderContext, StyleResource} from '..';

/* eslint-disable-next-line prefer-named-capture-group */
const regex = /@(?:import|require)\s+(?:\([a-z,\s]+\)\s*)?['"]?([^'"\s;]+)['"]?;?/gu;

export const resolveImportUrl = (ctx: LoaderContext, {file, content}: StyleResource): StyleResource => ({
    file,
    content: content.replace(regex, (match: string, pathToResource?: string) => {
        if (!pathToResource || /^[~/]/u.test(pathToResource)) {
            return match;
        }

        const absolutePathToResource = path.resolve(path.dirname(file), pathToResource); // 解析爲一個絕對路徑
        const relativePathFromContextToResource = path
            .relative(ctx.context, absolutePathToResource)  // 根據當前工作目錄返回 ctx.context(當前上下文路徑)到 absolutePathToResource 的相對路徑
            .split(path.sep)
            .join('/');

        return match.replace(pathToResource, relativePathFromContextToResource);
    }),
});
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章