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);
}),
});