With Vue, style isolation is achieved by enclosing scoped in the
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style scoped>
.about {
color: red
}
</style>
Copy the code
Chrome:
Let’s debug the source code to analyze how this is implemented.
Vue-loader source code process analysis
First, a vue-loader work flow chart, first mixed impression, look back.
Check the configuration
I don’t know if you have seen the default webpack configuration of vue-cli, you can output the configuration of the project through the file via vue inspect > config.js command, @vue/cli 4.5.10 default configuration is as follows. Focus only on the configuration of.vue files
module.exports = {
module: {
rules: [
/* config.module.rule('vue') */
{
test: /\.vue$/,
use: [
{
loader: 'D:\\work_space\\qiankun-example\\sub-vue\\node_modules\\cache-loader\\dist\\cjs.js'.options: {
// Save path of the previously packed result
cacheDirectory: 'D:\\work_space\\qiankun-example\\sub-vue\\node_modules\\.cache\\vue-loader'.cacheIdentifier: 'ffa56dac'}}, {loader: 'D:\\work_space\\qiankun-example\\sub-vue\\node_modules\\vue-loader\\lib\\index.js'.options: {
compilerOptions: {
whitespace: 'condense'
},
cacheDirectory: 'D:\\work_space\\qiankun-example\\sub-vue\\node_modules\\.cache\\vue-loader'.cacheIdentifier: 'ffa56dac'}}]},]},plugins: [
/* config.plugin('vue-loader') */
new VueLoaderPlugin(),
/ /...],}Copy the code
Two points can be seen from the configuration
vue-loader
And aftercache-loader
, this loader is mainly used to cache the results of the last compilation. For files that have not been modified, it can be directly pulled from the cache, greatly shortening the compilation time and improving the performance of the most obvious loader.- To deal with
vue
The file also needsvue-loader
andVueLoaderPlugin
Plug-ins are indispensable
VueLoaderPlugin
The main functions of this plug-in are as follows
- to
The Compiler in webpack
Object to add an identity,vue-loader
Registration is monitored during executionVueLoaderPlugin
- detection
compiler
therules
Is there any processing in.vue
The loader of the file does not throw an error - Add a
RuleSet
Global formatpitcher-loader
, such asimport Foo from './foo.css? vue
Will match this loader withvue
parameter - A copy of
Ruleset
The format ofrules
Pre-coveredrules
Source code fragment analysis:
sub-vue\node_modules\vue-loader\lib\plugin-webpack4.js
:
// Add a tag to check whether the vueLoaderPlugin is configured
if (compiler.hooks) {
// webpack 4
compiler.hooks.compilation.tap(id, compilation= > {
const normalModuleLoader = compilation.hooks.normalModuleLoader
normalModuleLoader.tap(id, loaderContext= > {
loaderContext[NS] = true})})}// A global pitcher-loader is used to process template, script, and style blocks in vue
const pitcher = {
loader: require.resolve('./loaders/pitcher'),
resourceQuery: query= > {
// This loader will only hit requests in this format. The vue field is present in query
// import script from \"./App.vue? vue&type=script&lang=js
const parsed = qs.parse(query.slice(1))
returnparsed.vue ! =null
},
options: {
// Cache storage path
// sub-vue\node_modules\.cache\vue-loader
cacheDirectory: vueLoaderUse.options.cacheDirectory,
}
}
// Replace rules with ruleset rules
compiler.options.module.rules = [
pitcher,
...clonedRules,
...rules
]
}
Copy the code
VueLoader
After configuring the plug-in, NPM run build enters the buildModule phase of Webpack, where loaderRunner is used to parse the source file.
Both of the following requests go through vue-loader with or without resourceQuery
import App from './App.vue
import * from "./App.vue? vue&type=template&id=1511d40d&
import App from './App.vue
- parsing
resourceQuery
Query (); query (){}
- through
vue-template-compiler
theparseComponent
the.vue
The SFC is resolved into three chunksTemplate, script, style
.script
Also generatesmapping
Pass to the later loader, which is used hereLRU
Cache algorithm, because the same source file will continue to be called laterparse
Method that requires caching to prevent performance waste while still usinghash-sum
The module calculates the unique value corresponding to the source filecacheKey
If the source file is modified, the cache is automatically invalidated.
// sub-vue\node_modules\@vue\component-compiler-utils\dist\parse.js
const cache = new (require('lru-cache'(a))100);
function parse(options) {
/ /... ignore
// Cache the compiled results
const cacheKey = hash(filename + source + JSON.stringify(compilerParseOptions));
let output = cache.get(cacheKey);
if (output)
return output;
output = compiler.parseComponent(source, compilerParseOptions);
Copy the code
- because
resourceQuery
Empty won’t go here logic
// sub-vue\node_modules\vue-loader\lib\index.js
// if the query has a type field, this is a language block request
// e.g. foo.vue? type=template&id=xxxxx
// and we will return early
if (incomingQuery.type) {
returnselectBlock( descriptor, loaderContext, incomingQuery, !! options.appendExtension ) }Copy the code
- To calculate
css-scoped id
forcss
Isolation, id is similardata-v-039c5b43
In the039c5b43
// sub-vue\node_modules\vue-loader\lib\index.js
const id = hash(
isProduction
? (shortFilePath + '\n' + source.replace(/\r\n/g.'\n'))
: shortFilePath
)
Copy the code
- Assemble the string to be returned this time, and convert template, script, and style to the request with parameters, mainly plus
? vue&type=template&id=1511d40d&
suchresourceQuery
. If you are in development mode, you will wear itvue-loader
Implement the module hot replacement interface itself, along with a runtime sectionnormalize component
The function is used to format the values exposed by the component.
- Request at this time
import App from './App.vue
.vue-loader
The returned JSON string looks like this. It breaks down one request into three requests and passestype=
Distinguish between
import * from "./App.vue? vue&type=template&id=1511d40d&
This module request is still resolved by vue-loader, but in a different order. The above request has a global pitcher-loader registered by vUE. This is a special loader. Normal loaders are executed in reverse order. But if there is a pitcher-loader order that changes, here are the differences
Suppose you configure a rule to handle CSS as follows
{
test: /\.css$/,
use: [normalLoader1, normalLoader2, normalLoader3, normalLoader4]
}
Copy the code
So it’s a sequential execution in reverse order
If you encounter pitcher-loader
{
test: /\.css$/,
use: [normalLoader1, pitcherLoader, normalLoader3, normalLoader4]
}
Copy the code
NormalLoader3, normalLoader4, normalLoader4, normalLoader3, normalLoader4 And then normalLoader1. LoaderRunner sub-vue\node_modules\loader-runner\lib\LoaderRunner. Generally, there is no normalLoader after pitcher-loader. The common pticher-loader is the style-loader used in development mode, which inserts CSS into the head using the style tag.
import * from “./App.vue? The following things happen when vue&type= template&ID = 1511d40D&
- The pitcher-loader is processed first, and the pitcher-loader is processed as follows
- Filter loader to remove eslint-loader and itself
- If type is template, the logical part of the template will be matched, the request will be reserialized, and the cache-loader and templateLoader will be converted into three inline-loaders
-! . /node_modules/cache-loader/dist/cjs.js? {\ \"cacheDirectory\\": \ \"node_modules/.cache/vue-loader\\", \ \"cacheIdentifier\\": \ \"7e12f88b-vue-loader-template\\"}! . /node_modules/vue-loader/lib/loaders/templateLoader.js?? vue-loader-options! . /node_modules/cache-loader/dist/cjs.js?? ref-00! . /node_modules/vue-loader/lib/index.js?? vue-loader-options! ./App.vue? vue&type=template&id=1511d40d& Copy the code
Template processing flow in Vue:
Among them. ,! ,!!!!! Refer to wepack-inline-loader for symbol meaning
To the template tag element_scoped
- The transformed in-line request is returned first
vue-loader
Processing, we’ve calculated it beforedescriptor
As a result, the cache returns directly.
- So if YOU go into the selectBlock, the slectBlock will handle each type,
loaderContext.callback
You can knowvue-loader
Is an asynchronous loader,templateLoader
It waits for it to complete.
template-loader
Will receivevue-loader
As a result, his role isvue-template-compiler
afterParser, Optimization, generate
The phase will returnrender
Functions, and static nodesstaticRender
You may notice that scopedId is also passed to compiler. If the render function is generated with data-V-1511d40D, it is not. Compiler adds {attrs: {‘ data-V-1511d40d ‘}} instead of the render function for the size of the package, but adds the patch phase of the _scopedId runtime.
Back to the normalize component above, when the code browser runs, it will see that $options has a _scopedId, which will be set dynamically on the first patch.
Since then we’ve known what’s on the label_scoped
Where does id come from, and how do CSS property selectors come from?
Add the CSS styles_scopedId
SRC \\ app.vue? SRC \\ app.vue? SRC \\ app.vue? Vue&type =style&index=0&lang= CSS & converts the request to an inline request, as follows
"-! . /node_modules/mini-css-extract-plugin/dist/loader.js?? ref--6-oneOf-1-0! . /node_modules/css-loader/dist/cjs.js?? ref--6-oneOf-1-1 ! . /node_modules/vue-loader/lib/loaders/stylePostLoader.js ! . /node_modules/postcss-loader/src/index.js?? ref--6-oneOf-1-2 ! . /node_modules/cache-loader/dist/cjs.js?? ref--0-0 ! . /node_modules/vue-loader/lib/index.js?? vue-loader-options! ./App.vue? vue&type=style&index=0&lang=css"Copy the code
npm run build
Mode, style will pass through 5 loaders. 再vue-loader
When processed, it simply returns the cache as it did with the Template process. while_scpoedId
Is in thestyle-post-loader
It was added during processing
// sub-vue\node_modules\vue-loader\lib\loaders\stylePostLoader.js
const qs = require('querystring')
const { compileStyle } = require('@vue/component-compiler-utils')
// This is a post-loader to convert CSS to csS-scoped
module.exports = function (source, inMap) {
const query = qs.parse(this.resourceQuery.slice(1))
// Reassemble the CSS using the postCSS parser
const { code, map, errors } = compileStyle({
source,
filename: this.resourcePath,
id: `data-v-${query.id}`.map: inMap,
scoped:!!!!! query.scoped,trim: true})}Copy the code
The method compileStyle returns new CSS using the PostCSS capability. Postcss is similar to Babel-Loader in that it handles CSS syntax such as variables and browser prefixes. It can also parse CSS into CSS-AST, edit different node types through the plug-in mechanism, and regenerate new CSS through generate. Vue uses a custom postCSS-plugin to add _scopedId and /deep/ capabilities to CSS
A postCSS-plugin plugin looks like this
module.exports = (opts = {}) = > {
return {
postcssPlugin: 'Plug-in name',
prepare (result) {
// We can put some common logic here
return {
Declaration (node) {},
Rule (node) {},
AtRule (node) {}
}
}
}
}
Copy the code
CSS AST is much simpler than JS. There are several main types:
- The @ symbol style begins, such as @meida, media queries
- Rule is the name of the CSS class
- Dec is the concrete style
padding: 5px
This kind of
Just write a plugin, iterate over these types, modify CSS-AST, and add _scopedId. See the implementation of \node_modules\@vue\component-compiler-utils\lib\stylePlugins\scoped.ts for details.
At this point CSS style isolation is implemented. Jingdong recently looked at the set of micro front-end implementation, its CSS isolation is similar, but does not rely on postCSS, their own self-service cssParser, replaced by regular violence.
conclusion
Since vue-loader is strongly related to Webpack, it is necessary to know some knowledge of the Webpack process, otherwise it is really difficult to learn. By the way, have you made the Vite yet?