This article will take the latest vue-CLI version @vue/cli-service 4.5.8 (later referred to as CLI4) as the context to share the detailed. Vue file parsing and compilation process. Resolution refers to the vue file is resolved as the template | script | style three copies of the source code, compile refers to the rendering function is the template source code is compiled.
Some notes written up front:
- This article does not involve too much compilation details, the main purpose is to help you get familiar with the compilation process, to solve the problem to provide ideas on the direction of compilation.
- This article uses Vue
2.6.11
Is not related to Vue3. - Reading this article requires reading
Webpack
和Vue
Have some understanding.
1. Configure processing rules in CLI4
The project template generated by CLI4 is based on Webpack, we all know that Webpack processing. Vue file needs loader, but CLI4 packaging is very thorough, we can not easily find the Webpack configuration file in the project directory. So the first step is to find loader.
1-1. package.json
"scripts": {
"serve": "vue-cli-service serve"."build": "vue-cli-service build"."lint": "vue-cli-service lint"
},
Copy the code
The actual command to run yarn build is vue-cli-service build. Vue-cli-service is a command provided by a package in node_modules.
1-2. node_modules/.bin/vue-cli-service
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/.. /@vue/cli-service/bin/vue-cli-service.js" "$@"
ret=$?
else
node "$basedir/.. /@vue/cli-service/bin/vue-cli-service.js" "$@"
ret=$?
fi
Copy the code
All commands provided by node_modules can be found in node_modules/.bin, Nodenode_modules /@vue/cli-service/bin/vue-cli-service.js build, node_modules/@vue/cli-service/bin This is already an instruction that Node recognizes.
1-3. node_modules/@vue/cli-service/bin/vue-cli-service.js
// ...
const Service = require('.. /lib/Service')
// Create a Service instance
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())
// Get command carrying parameters
const rawArgv = process.argv.slice(2)
// ...
service.run(command, args, rawArgv).catch(err= > {
error(err)
process.exit(1)})Copy the code
- Create a Service instance.
- Get command
vue-cli-service
Carrying parametersbuild
. - call
service.run
Methods.
1-4. node_modules/@vue/cli-service/lib/Service.js
class Service {
constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {...// Assign member variables to plugins
this.plugins = this.resolvePlugins(plugins, useBuiltIn)
...
}
Copy the code
In the constructor of the Service, the member variable plugins is initialized by calling the resolvePlugins method.
resolvePlugins (inlinePlugins, useBuiltIn) {
const idToPlugin = id= > ({
id: id.replace(/ ^. / / /.'built-in:'),
apply: require(id)
})
let plugins
// Default plugins
const builtInPlugins = [
'./commands/serve'.'./commands/build'.'./commands/inspect'.'./commands/help'.// config plugins are order sensitive
'./config/base'.'./config/css'.'./config/prod'.'./config/app'
].map(idToPlugin)
if(inlinePlugins) { plugins = useBuiltIn ! = =false
? builtInPlugins.concat(inlinePlugins)
: inlinePlugins
} else {
const projectPlugins = Object.keys(this.pkg.devDependencies || {})
.concat(Object.keys(this.pkg.dependencies || {}))
.filter(isPlugin)
.map(id= > {
if (
this.pkg.optionalDependencies &&
id in this.pkg.optionalDependencies
) {
let apply = () = > {}
try {
apply = require(id)
} catch (e) {
warn(`Optional dependency ${id} is not installed.`)}return { id, apply }
} else {
return idToPlugin(id)
}
})
plugins = builtInPlugins.concat(projectPlugins)
}
// Local plugins
if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
const files = this.pkg.vuePlugins.service
if (!Array.isArray(files)) {
throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got The ${typeof files}. `)
}
plugins = plugins.concat(files.map(file= > ({
id: `local:${file}`.apply: loadModule(`. /${file}`.this.pkgContext)
})))
}
return plugins
}
Copy the code
The resolvePlugins function consolidates the four types of plugins into an array and returns:
- Initialize the
Service
Class to pass in plugins - CLI4 default plugins
- The plugins devDependencies
- VuePlugins depend on plugins in
Each element of the array has an apply method that loads the corresponding plugin.
async run (name, args = {}, rawArgv = []) {
// ...
// load env variables, load user config, apply plugins
this.init(mode)
// ...
// Remove build handler from commands (name = 'build')
let command = this.commands[name]
// ...
const { fn } = command
return fn(args, rawArgv)
}
Copy the code
The run method calls init to attach the build handler to the member variable commands, fetch the build handler from commands, and execute.
init (mode = process.env.VUE_CLI_MODE) {
// ...
// Load the user Webpack configuration
const userOptions = this.loadUserOptions()
// Merge with the default configuration
this.projectOptions = defaultsDeep(userOptions, defaults())
// ...
// apply plugins.
this.plugins.forEach(({ id, apply }) = > {
if (this.pluginsToSkip.has(id)) return
apply(new PluginAPI(id, this), this.projectOptions)
})
// ...
}
Copy the code
Init loads the user configuration, then loops through the Apply method of plugins, passing in the configuration as a parameter.
These plug-ins have two main behaviors:
- call
PluginAPI
的registerCommand
Method, the corresponding module of the command (@vue/cli-service/lib/commands/*
) onService
Member variable ofcommands
In the. - call
PluginAPI
的chainWebpack
Method to add the respective Webpack chain configuration (@vue/cli-service/config/*
)push
到Service
Member variable ofwebpackChainFns
In the.
Let’s look at the logic that the build command performs.
1-5. node_modules/@vue/cli-service/lib/commands/build/index.js
(api, options) => {
// Register build command
api.registerCommand('build', {
description: 'build for production'.usage: 'vue-cli-service build [options] [entry|pattern]'.options: {
'--mode': `specify env mode (default: production)`.'--dest': `specify output directory (default: ${options.outputDir}) `.'--modern': `build app targeting modern browsers with auto fallback`.'--no-unsafe-inline': `build app without introducing inline scripts`.'--target': `app | lib | wc | wc-async (default: ${defaults.target}) `.'--inline-vue': 'include the Vue module in the final bundle of library or web component target'.'--formats': `list of output formats for library builds (default: ${defaults.formats}) `.'--name': `name for lib or web-component mode (default: "name" in package.json or entry filename)`.'--filename': `file name for output, only usable for 'lib' target (default: value of --name)`.'--no-clean': `do not remove the dist directory before building the project`.'--report': `generate report.html to help analyze bundle content`.'--report-json': 'generate report.json to help analyze bundle content'.'--skip-plugins': `comma-separated list of plugin names to skip for this run`.'--watch': `watch for changes`.'--stdin': `close when stdin ends`}},async (args, rawArgs) => {
// Execute the command callback
// Merge the default parameters into args
for (const key in defaults) {
if (args[key] == null) {
args[key] = defaults[key]
}
}
...
await build(args, api, options)
}
Copy the code
Call PluginAPI. Registered registerCommand method build command and callback, in the callback to add some default option args (such as target: ‘app’), and then execute the file under the build method.
async function build (args, api, options) {...// resolve raw webpack config
let webpackConfig
if (args.target === 'lib') {
webpackConfig = require('./resolveLibConfig')(api, args, options)
} else if (
args.target === 'wc' ||
args.target === 'wc-async'
) {
webpackConfig = require('./resolveWcConfig')(api, args, options)
} else {
webpackConfig = require('./resolveAppConfig')(api, args, options)
}
...
return new Promise((resolve, reject) = > {
webpack(webpackConfig, (err, stats) = >{... })})}Copy the code
The build method matches the configuration file against the value of args.target and performs the packaging using the Webpack Nodejs Api. / resolveappconfig.js is loaded by default and webpack is called to perform the packing.
1-6. node_modules/@vue/cli-service/lib/commands/build/resolveAppConfig.js
module.exports = (api, args, options) = >{...const config = api.resolveChainableWebpackConfig()
...
return api.resolveWebpackConfig(config)
})
Copy the code
The file called PluginAPI resolveChainableWebpackConfig method to obtain the Webpack chain configuration, Before returning, the PluginAPI resolveWebpackConfig method is called to convert the chained configuration to JSON configuration. Let’s look at the concrete implementation of these two methods.
1-7. node_modules/@vue/cli-service/lib/PluginAPI.js
resolveWebpackConfig (chainableConfig) {
return this.service.resolveWebpackConfig(chainableConfig)
}
resolveChainableWebpackConfig () {
return this.service.resolveChainableWebpackConfig()
}
Copy the code
The two methods in PluginAPI that get the configuration actually call methods of the same name in the Service.
1-8. node_modules/@vue/cli-service/lib/Service.js
resolveChainableWebpackConfig () {
const chainableConfig = new Config()
// apply chains
this.webpackChainFns.forEach(fn= > fn(chainableConfig))
return chainableConfig
}
Copy the code
In 1-4, we mentioned that webpackChainFns stores the chain configuration under node_modules/@vue/cli-service/lib/config/. ResolveChainableWebpackConfig function is constructed a Webpack Config object, and using the object implementation chain configuration, Including node_modules / @ vue/cli – service/lib/config/base on processing in js. Vue file configuration:
webpackConfig.module
.rule('vue')
.test(/\.vue$/)
.use('cache-loader')
.loader(require.resolve('cache-loader'))
.options(vueLoaderCacheConfig)
.end()
.use('vue-loader')
.loader(require.resolve('vue-loader'))
.options(Object.assign({
compilerOptions: {
whitespace: 'condense'
}
}, vueLoaderCacheConfig))
webpackConfig
.plugin('vue-loader')
.use(require('vue-loader').VueLoaderPlugin)
Copy the code
The original CLI4 also uses vue-loader to process.vue files, but it also relies on a plugin for VueLoaderPlugin compared to CLI3.
resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) {
...
let config = chainableConfig.toConfig()
...
return config
}
Copy the code
The resolveWebpackConfig method is simpler, calling toConfig directly and returning.
VueLoaderPlugin rewrite rule
As mentioned above, CLI4 relies on the VueLoaderPlugin plugin in addition to CLI3, and this plugin is initialized in process 1-8, so let’s first look at what this plugin does.
2-1. node_modules/vue-loader/lib/plugin.js
if (webpack.version && webpack.version[0] > 4) {
// webpack5 and upper
VueLoaderPlugin = require('./plugin-webpack5')}else {
// webpack4 and lower
VueLoaderPlugin = require('./plugin-webpack4')}Copy the code
This article uses Webpack 4.44.2 to match the plug-in version based on the Webpack version.
2-2. node_modules/vue-loader/lib/plugin-webpack4.js
class VueLoaderPlugin {
apply (compiler) {
// ...
const vueLoaderUse = vueUse[vueLoaderUseIndex]
vueLoaderUse.ident = 'vue-loader-options'
vueLoaderUse.options = vueLoaderUse.options || {}
// create a cloned rule
const clonedRules = rules
.filter(r= >r ! == vueRule) .map(cloneRule)const pitcher = {
loader: require.resolve('./loaders/pitcher'),
resourceQuery: query= > {
const parsed = qs.parse(query.slice(1))
returnparsed.vue ! =null
},
options: {
cacheDirectory: vueLoaderUse.options.cacheDirectory,
cacheIdentifier: vueLoaderUse.options.cacheIdentifier
}
}
// replace original rules
compiler.options.module.rules = [
pitcher,
...clonedRules,
...rules
]
}
}
Copy the code
The VueloaderPlugin. apply method overrides the configuration of the loaders for the current instance when the Webpack plug-in executes the Apply method on the plug-in (function) prototype chain at initialization.
- To deal with
.vue
The loader configuration of the file is separated and stored in variablesvueLoaderUse
In the. compiler.options.module.rules
The rest of the rules are copied to variablesclonedRules
In the.- Based on the
vueLoaderUse
The options set by the user in generates a new rulepitcher
。 - rewrite
compiler.options.module.rules
。
The rewritten rules have two rules related to Vue:
vue-loader/lib/loaders/pitcher.js
(This article is new).- Vue-loader and cache-loader in the original Webpack configuration.
Can be described as:
{
test: /\.vue$/,
use: [
'vue-loader/lib/loaders/pitcher.js',]}, {test: /\.vue$/,
use: [
'vue-loader/lib/index.js'.'cache-loader/dist/cjs.js']}Copy the code
3. Parse the Vue file
The loaders that process files have been clarified above, so let’s follow these loaders to see how they parse and compile.
3-1. node_modules/vue-loader/lib/index.js
const { parse } = require('@vue/component-compiler-utils')...module.exports = function (source) {...const {
target,
request,
minimize,
sourceMap,
rootContext,
resourcePath,
resourceQuery
} = loaderContext
const rawQuery = resourceQuery.slice(1)
// Obtain the loader parameters
const incomingQuery = qs.parse(rawQuery)
...
const descriptor = parse({
source,
// The user configured compiler is used by default
compiler: options.compiler || loadTemplateCompiler(loaderContext),
filename,
sourceRoot,
needMap: sourceMap
})
// If the specified type exists during loader configuration
if (incomingQuery.type) {
returnselectBlock( descriptor, loaderContext, incomingQuery, !! options.appendExtension ) } ...// template
let templateImport = `var render, staticRenderFns`
let templateRequest
if (descriptor.template) {
const src = descriptor.template.src || resourcePath
const idQuery = `&id=${id}`
const scopedQuery = hasScoped ? `&scoped=true` : ` `
const attrsQuery = attrsToQuery(descriptor.template.attrs)
const query = `? vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
const request = templateRequest = stringifyRequest(src + query)
templateImport = `import { render, staticRenderFns } from ${request}`}...let code = `
${templateImport}
${scriptImport}
${stylesCode}. `. code +=`\nexport default component.exports`
return code
}
Copy the code
It can be seen that vue-loader mainly has three responsibilities:
- call
@vue/component-compiler-utils
的parse
function - If yes, loader parameters exist
type
Property is executedselectBlock
Function to select source code (for example, from the template tag in a Vue file, depending on and aboveparse
Analytic result of function) - According to the
parse
Returns the result by string, and returns
The parse function has a compiler parameter that takes the user-configured compiler by default, and if not loads a default compiler via loadTemplateCompiler.
function loadTemplateCompiler (loaderContext) {
try {
return require('vue-template-compiler')}catch (e) {
if (/version mismatch/.test(e.toString())) {
loaderContext.emitError(e)
} else {
loaderContext.emitError(new Error(
`[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
`or a compatible compiler implementation must be passed via options.`))}}}Copy the code
LoadTemplateCompiler loads the vue-template-Compiler library and gives a hint when loading errors occur.
Parse the returned result is an object, it records the three special label template | script | style content in Vue file location, so that subsequent loader can through location information to choose the right content.
3-2. node_modules/@vue/component-compiler-utils/dist/parse.js
function parse(options) { const { source, filename = '', compiler, compilerParseOptions = { pad: 'line' }, sourceRoot = '', needMap = true } = options; const cacheKey = hash(filename + source + JSON.stringify(compilerParseOptions)); let output = cache.get(cacheKey); if (output) return output; output = compiler.parseComponent(source, compilerParseOptions); if (needMap) { if (output.script && ! output.script.src) { output.script.map = generateSourceMap(filename, source, output.script.content, sourceRoot, compilerParseOptions.pad); } if (output.styles) { output.styles.forEach(style => { if (! style.src) { style.map = generateSourceMap(filename, source, style.content, sourceRoot, compilerParseOptions.pad); }}); } } cache.set(cacheKey, output); return output; }Copy the code
Parse checks the cache first and returns the contents if there is one, or if there is none:
- The implementation of VueTemplateCompiler
parseComponent
Function to get the parsing result. - Perform sourceMap processing.
3-3. node_modules/vue-template-compiler/build.js
function parseComponent (content, options) {
if ( options === void 0 ) options = {};
var sfc = {
template: null.script: null.styles: [].customBlocks: [].errors: []};var depth = 0;
var currentBlock = null;
var warn = function(msg) {
sfc.errors.push(msg);
}
function start(. args) {
// Process the start tag, save the label block object
}
function end(tag, start) {
// Process the end tag and modify the label block object
}
function checkAttrs (block, attrs){
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if (attr.name === 'lang') {
block.lang = attr.value;
}
if (attr.name === 'scoped') {
block.scoped = true;
}
if (attr.name === 'module') {
block.module = attr.value || true;
}
if (attr.name === 'src') { block.src = attr.value; }}}function padContent(block, pad) {
// Fill empty lines to keep the line numbers of the separated template and script blocks unchanged (easy for sourceMap mapping)
}
parseHTML(content, {
warn: warn,
start: start,
end: end,
outputSourceRange: options.outputSourceRange
});
return sfc
}
Copy the code
ParseComponent takes two arguments. The first argument, Content, is the source code of the Vue file. The second argument, options, defaults to {pad: ‘line’} and is a user-configurable parsing option. This function creates an SFC object to hold the result of parsing the Vue file. Its structure is described as follows:
interface SFCDescriptor {
filename: string
source: string
template: SFCBlock
script: SFCBlock
scriptSetup: SFCBlock
styles: SFCBlock[]
customBlocks: SFCBlock[]
}
interface SFCBlock {
type: 'template' | 'script' | 'style'
attrs: { lang: string.functional: boolean },
content: string.// Content, equal to html.slice(start, end)
start: number.// Start offset
end: number.// End offset
lang: string
}
Copy the code
The warn, start, and end functions are also declared and passed in as parameters to parseHTML, so let’s go into parseHTML to see what’s going on.
function parseHTML (html, options) {
var stack = [];
var expectHTML = options.expectHTML;
var isUnaryTag$$1 = options.isUnaryTag || no;
var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
var index = 0;
var last, lastTag;
while (html) {
last = html;
// Make sure we're not in a plaintext content element like script/style
if(! lastTag || ! isPlainTextElement(lastTag)) {var textEnd = html.indexOf('<');
if (textEnd === 0) {
// Comment:
if (comment.test(html)) {
var commentEnd = html.indexOf('-->');
if (commentEnd >= 0) {
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3);
}
advance(commentEnd + 3);
continue}}// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
if (conditionalComment.test(html)) {
var conditionalEnd = html.indexOf('] > ');
if (conditionalEnd >= 0) {
advance(conditionalEnd + 2);
continue}}/ / processing Doctype:.// Handle End tag:.// Handle the Start tag:
var startTagMatch = parseStartTag();
if (startTagMatch) {
handleStartTag(startTagMatch);
if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
advance(1);
}
continue}}... }else{... parseEndTag(stackedTag, index - endTagLength, index); }// Clean up any remaining tags
parseEndTag();
function advance (n) {
index += n;
html = html.substring(n);
}
function parseStartTag () {... }function handleStartTag (match) {... }function parseEndTag (tagName, start, end) {... }}Copy the code
ParseHTML main duties and responsibilities of the template was isolated from the Vue file | script | style code, in the three tag is to match the start and end tags, And put these information through the incoming start | end function records the SFC in parseComponent variable (of course also record label attribute lang | scoped | module | SRC).
The key is advance, which changes the offset index and removes the processed code from the HTML (which is the content of the Vue file).
See sequence diagram for detailed steps:
After the execution of parseHtml, all the information of the Vue file is recorded in the object SFC of parseComponent, and the result is returned step by step to 3-1. After 3-1 obtains the SFC object, it will use the information for string splicing. Finally, a new module file (code) is generated and handed to the next loader.
At this point, we will have fully parsed the Vue file! The last two sections of this section show how the obtained template source code is compiled by calling the Vue source code (which falls under the Vue source code category).
Changes in file contents before and after parsing:
app.vue
<template>
<div id="app">
<h1>Vue Compile Share</h1>
<Details></Details>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import Details from "./components/details.vue";
export default Vue.extend({
name: "app",
components: {
Details
}
});
</script>
<style lang="stylus">
#app {
font-family Avenir, Helvetica, Arial, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
text-align center
color red
margin-top 60px
}
</style>
Copy the code
Compiled intermediate code
import { render, staticRenderFns } from "./App.vue? vue&type=template&id=7a0f6a6c&"
import script from "./App.vue? vue&type=script&lang=ts&"
export * from "./App.vue? vue&type=script&lang=ts&"
import style0 from "./App.vue? vue&type=style&index=0&lang=stylus&"
/* normalize component */
import normalizer from ! "" . /node_modules/vue-loader/lib/runtime/componentNormalizer.js"
var component = normalizer(
script,
render,
staticRenderFns,
false.null.null.null
)
export default component.exports
Copy the code
As you can see, the import is of type type, so it will be handled by the selectBlock mentioned above, but this is just a string generated. How does it go through the VueLoader again? Let’s move on.
3-4 node_modules/vue-loader/lib/loaders/pitcher.js
module.exports.pitch = function (remainingRequest) {...// Handle tempalte
if (query.type === `template`) {
const path = require('path')
const cacheLoader = cacheDirectory && cacheIdentifier
? [`The ${require.resolve('cache-loader')}?The ${JSON.stringify({
// For some reason, webpack fails to generate consistent hash if we
// use absolute paths here, even though the path is only used in a
// comment. For now we have to ensure cacheDirectory is a relative path.
cacheDirectory: (path.isAbsolute(cacheDirectory)
? path.relative(process.cwd(), cacheDirectory)
: cacheDirectory).replace(/\\/g.'/'),
cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
})}`]
: []
const preLoaders = loaders.filter(isPreLoader)
const postLoaders = loaders.filter(isPostLoader)
const request = genRequest([
...cacheLoader,
...postLoaders,
templateLoaderPath + `?? vue-loader-options`. preLoaders ])// console.log(request)
// the template compiler uses esm exports
return `export * from ${request}`}... }Copy the code
Above, we can say the handling rules and a Vue, namely using the Vue – loader/lib/loaders/pitcher. Js finally, Import {render, staticRenderFns} from “./ app.vue? Vue&type = template&ID = 7A0F6a6c& “will enter the flow here, And eventually be replaced by using the vue – loader/lib/index, js and vue – loader/lib/loaders/templateLoader js to deal with.
So the Vue file is processed again by VueLoader. This time it will be parsed again, but as we mentioned above parse is cached, so the second time it will hit the cache and return the result of the first parse. So will be executed selectBlock method and returns the template | script | style of the source code.
3-5. node_modules/vue-loader/lib/select.js
module.exports = function selectBlock (descriptor, loaderContext, query, appendExtension) {
// template
if (query.type === `template`) {
if (appendExtension) {
loaderContext.resourcePath += '. ' + (descriptor.template.lang || 'html')
}
loaderContext.callback(
null,
descriptor.template.content,
descriptor.template.map
)
return
}
// script.// styles.// custom. }Copy the code
The template/script/style code separated from the Vue file is passed to the next loader via loaderContext.callback. The vue – loader/lib/loaders/templateLoader js.
3-6. node_modules/vue-loader/lib/loaders/templateLoader.js
module.exports = function (source) {
// ...
// allow using custom compiler via options
const compiler = options.compiler || require('vue-template-compiler')
const compilerOptions = Object.assign({
outputSourceRange: true
}, options.compilerOptions, {
scopeId: query.scoped ? `data-v-${id}` : null.comments: query.comments
})
// for vue-component-compiler
const finalOptions = {
source,
filename: this.resourcePath,
compiler,
compilerOptions,
// allow customizing behavior of vue-template-es2015-compiler
transpileOptions: options.transpileOptions,
transformAssetUrls: options.transformAssetUrls || true,
isProduction,
isFunctional,
optimizeSSR: isServer && options.optimizeSSR ! = =false.prettify: options.prettify
}
const compiled = compileTemplate(finalOptions)
// ...
const { code } = compiled
// finish with ESM exports
return code + `\nexport { render, staticRenderFns }`
}
Copy the code
As you can see, the loader generates a configuration object for compilation, passes it to the @vue/component-compiler-utils library compileTemplate, and returns the result with minor changes.
3-7. node_modules/@vue/component-compiler-utils/dist/compileTemplate.js
function compileTemplate(options) {
const { preprocessLang } = options;
const preprocessor = preprocessLang && consolidate[preprocessLang];
if (preprocessor) {
return actuallyCompile(Object.assign({}, options, {
source: preprocess(options, preprocessor)
}));
}
else if (preprocessLang) {
// Remind specific language of preprocessing
}
else {
returnactuallyCompile(options); }}Copy the code
Check for preprocessor languages:
- If a preprocessor is present, it is preprocessed and then compiled.
- If there is no preprocessor, an error message is displayed.
- If not, compile is performed.
const assetUrl_1 = __importDefault(require("./templateCompilerModules/assetUrl"));
const srcset_1 = __importDefault(require("./templateCompilerModules/srcset"));
function actuallyCompile(options) {
const { source, compiler, compilerOptions = {}, transpileOptions = {}, transformAssetUrls, transformAssetUrlsOptions, isProduction = process.env.NODE_ENV === 'production', isFunctional = false, optimizeSSR = false, prettify = true } = options;
const compile = optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile;
let finalCompilerOptions = compilerOptions;
if (transformAssetUrls) {
const builtInModules = [
transformAssetUrls === true
? assetUrl_1.default(undefined, transformAssetUrlsOptions)
: assetUrl_1.default(transformAssetUrls, transformAssetUrlsOptions),
srcset_1.default(transformAssetUrlsOptions)
];
finalCompilerOptions = Object.assign({}, compilerOptions, {
modules: [...builtInModules, ...(compilerOptions.modules || [])],
filename: options.filename
});
}
const { ast, render, staticRenderFns, tips, errors } = compile(source, finalCompilerOptions);
// ...
return {
ast,
code,
source,
tips,
errors
};
}
Copy the code
In the function actuallyCompile, two special processing rules are merged into the finalCompilerOptions object, assetUrl_1 for handling resource paths and SRCset_1 for setting reactive images. We’ll talk about what they do to these values later.
When you get the new compiler.compile configuration item, the function compiler.compile in the configuration is called and the result is returned. If you are careful enough, you will see that compiler configuration items were added to templateloader.js in 3-2, It in the user not to explicitly specify the compiler defaults to using the vue – the template – the compiler (const compiler = options.com piler | | the require (‘ vue – the template – the compiler ‘)).
4. VueTemplateCompiler compiles templates
Analysis to be determined…