mirror of
https://github.com/vuejs/vue.git
synced 2024-11-21 20:28:54 +00:00
wip: port @vue/component-compiler-utils
This commit is contained in:
parent
50f2870ff0
commit
06594f68b7
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: pnpm run test:unit
|
run: pnpm run test:unit
|
||||||
|
|
||||||
ssr-test:
|
ssr-sfc-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@ -45,6 +45,9 @@ jobs:
|
|||||||
- name: Run SSR tests
|
- name: Run SSR tests
|
||||||
run: pnpm run test:ssr
|
run: pnpm run test:ssr
|
||||||
|
|
||||||
|
- name: Run compiler-sfc tests
|
||||||
|
run: pnpm run test:sfc
|
||||||
|
|
||||||
e2e-test:
|
e2e-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
@ -3,6 +3,5 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./temp",
|
"baseUrl": "./temp",
|
||||||
"types": []
|
"types": []
|
||||||
},
|
}
|
||||||
"include": ["src"]
|
|
||||||
}
|
}
|
||||||
|
37
package.json
37
package.json
@ -41,10 +41,11 @@
|
|||||||
"dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:compiler ",
|
"dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:compiler ",
|
||||||
"build": "node scripts/build.js",
|
"build": "node scripts/build.js",
|
||||||
"build:ssr": "npm run build -- runtime-cjs,server-renderer",
|
"build:ssr": "npm run build -- runtime-cjs,server-renderer",
|
||||||
"build:types": "rimraf temp && tsc --declaration --emitDeclarationOnly --outDir temp && api-extractor run",
|
"build:types": "rimraf temp && tsc --declaration --emitDeclarationOnly --outDir temp && api-extractor run && api-extractor run -c packages/compiler-sfc/api-extractor.json",
|
||||||
"test": "npm run ts-check && npm run test:types && npm run test:unit && npm run test:e2e && npm run test:ssr",
|
"test": "npm run ts-check && npm run test:types && npm run test:unit && npm run test:e2e && npm run test:ssr && npm run test:sfc",
|
||||||
"test:unit": "vitest run test/unit",
|
"test:unit": "vitest run test/unit",
|
||||||
"test:ssr": "npm run build:ssr && vitest run server-renderer",
|
"test:ssr": "npm run build:ssr && vitest run server-renderer",
|
||||||
|
"test:sfc": "vitest run compiler-sfc",
|
||||||
"test:e2e": "npm run build -- full-prod,server-renderer-basic && vitest run test/e2e",
|
"test:e2e": "npm run build -- full-prod,server-renderer-basic && vitest run test/e2e",
|
||||||
"test:transition": "karma start test/transition/karma.conf.js",
|
"test:transition": "karma start test/transition/karma.conf.js",
|
||||||
"test:types": "npm run build:types && tsc -p ./types/tsconfig.json",
|
"test:types": "npm run build:types && tsc -p ./types/tsconfig.json",
|
||||||
@ -85,45 +86,45 @@
|
|||||||
"csstype": "^3.1.0"
|
"csstype": "^3.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@microsoft/api-extractor": "^7.24.2",
|
"@microsoft/api-extractor": "^7.25.0",
|
||||||
"@rollup/plugin-alias": "^3.1.9",
|
"@rollup/plugin-alias": "^3.1.9",
|
||||||
"@rollup/plugin-commonjs": "^22.0.0",
|
"@rollup/plugin-commonjs": "^22.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^13.2.1",
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||||
"@rollup/plugin-replace": "^4.0.0",
|
"@rollup/plugin-replace": "^4.0.0",
|
||||||
"@types/he": "^1.1.2",
|
"@types/he": "^1.1.2",
|
||||||
"@types/node": "^17.0.30",
|
"@types/node": "^17.0.41",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.1.2",
|
||||||
"conventional-changelog-cli": "^2.2.2",
|
"conventional-changelog-cli": "^2.2.2",
|
||||||
"cross-spawn": "^7.0.3",
|
"cross-spawn": "^7.0.3",
|
||||||
"de-indent": "^1.0.2",
|
|
||||||
"enquirer": "^2.3.6",
|
"enquirer": "^2.3.6",
|
||||||
"esbuild": "^0.14.39",
|
"esbuild": "^0.14.43",
|
||||||
"execa": "^4.0.0",
|
"execa": "^4.1.0",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"jasmine-core": "^4.1.1",
|
"jasmine-core": "^4.2.0",
|
||||||
"jsdom": "^19.0.0",
|
"jsdom": "^19.0.0",
|
||||||
"karma": "^6.3.20",
|
"karma": "^6.3.20",
|
||||||
"karma-chrome-launcher": "^3.1.1",
|
"karma-chrome-launcher": "^3.1.1",
|
||||||
"karma-cli": "^2.0.0",
|
"karma-cli": "^2.0.0",
|
||||||
"karma-esbuild": "^2.2.4",
|
"karma-esbuild": "^2.2.4",
|
||||||
"karma-jasmine": "^5.0.1",
|
"karma-jasmine": "^5.0.1",
|
||||||
"lint-staged": "^12.4.1",
|
"lint-staged": "^12.5.0",
|
||||||
|
"postcss": "^8.4.14",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "^4.0.6",
|
"marked": "^4.0.16",
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"puppeteer": "^14.1.1",
|
"puppeteer": "^14.3.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^2.70.2",
|
"rollup": "^2.75.6",
|
||||||
"rollup-plugin-typescript2": "^0.31.2",
|
"rollup-plugin-typescript2": "^0.31.2",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.3.7",
|
||||||
"shelljs": "^0.8.5",
|
"shelljs": "^0.8.5",
|
||||||
"terser": "^5.13.1",
|
"terser": "^5.14.0",
|
||||||
"todomvc-app-css": "^2.4.2",
|
"todomvc-app-css": "^2.4.2",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.8.1",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.7.3",
|
||||||
"vitest": "^0.12.6",
|
"vitest": "^0.12.10",
|
||||||
"yorkie": "^2.0.0"
|
"yorkie": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
64
packages/compiler-sfc/api-extractor.json
Normal file
64
packages/compiler-sfc/api-extractor.json
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||||
|
|
||||||
|
"projectFolder": ".",
|
||||||
|
|
||||||
|
"mainEntryPointFilePath": "../../temp/packages/compiler-sfc/src/index.d.ts",
|
||||||
|
|
||||||
|
"compiler": {
|
||||||
|
"tsconfigFilePath": "../../api-extractor.tsconfig.json"
|
||||||
|
},
|
||||||
|
|
||||||
|
"dtsRollup": {
|
||||||
|
"enabled": true,
|
||||||
|
"untrimmedFilePath": "",
|
||||||
|
"publicTrimmedFilePath": "./dist/compiler-sfc.d.ts"
|
||||||
|
},
|
||||||
|
|
||||||
|
"apiReport": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"docModel": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"tsdocMetadata": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"messages": {
|
||||||
|
"compilerMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"extractorMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning",
|
||||||
|
"addToApiReportFile": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"ae-missing-release-tag": {
|
||||||
|
"logLevel": "none"
|
||||||
|
},
|
||||||
|
"ae-internal-missing-underscore": {
|
||||||
|
"logLevel": "none"
|
||||||
|
},
|
||||||
|
"ae-forgotten-export": {
|
||||||
|
"logLevel": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"tsdocMessageReporting": {
|
||||||
|
"default": {
|
||||||
|
"logLevel": "warning"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tsdoc-undefined-tag": {
|
||||||
|
"logLevel": "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,23 +8,28 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.16.4",
|
"@babel/parser": "^7.18.4",
|
||||||
"source-map": "^0.6.1",
|
"postcss": "^8.4.14",
|
||||||
"postcss": "^8.1.10"
|
"source-map": "^0.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/types": "^7.18.4",
|
||||||
"@types/estree": "^0.0.48",
|
"@types/estree": "^0.0.48",
|
||||||
"@babel/types": "^7.16.0",
|
"@types/hash-sum": "^1.0.0",
|
||||||
"@types/lru-cache": "^5.1.0",
|
"@types/lru-cache": "^5.1.1",
|
||||||
"estree-walker": "^2.0.2",
|
|
||||||
"magic-string": "^0.25.7",
|
|
||||||
"pug": "^3.0.1",
|
|
||||||
"sass": "^1.26.9",
|
|
||||||
"@vue/consolidate": "^0.17.3",
|
"@vue/consolidate": "^0.17.3",
|
||||||
|
"de-indent": "^1.0.2",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
|
"less": "^4.1.3",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^5.1.1",
|
||||||
|
"magic-string": "^0.25.9",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"postcss-modules": "^4.0.0",
|
"postcss-modules": "^4.3.1",
|
||||||
"postcss-selector-parser": "^6.0.4"
|
"postcss-selector-parser": "^6.0.10",
|
||||||
|
"pug": "^3.0.2",
|
||||||
|
"sass": "^1.52.3",
|
||||||
|
"stylus": "^0.58.1",
|
||||||
|
"vue-template-es2015-compiler": "^1.9.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
143
packages/compiler-sfc/src/compileStyle.ts
Normal file
143
packages/compiler-sfc/src/compileStyle.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
const postcss = require('postcss')
|
||||||
|
import { ProcessOptions, LazyResult } from 'postcss'
|
||||||
|
import trimPlugin from './stylePlugins/trim'
|
||||||
|
import scopedPlugin from './stylePlugins/scoped'
|
||||||
|
import {
|
||||||
|
processors,
|
||||||
|
StylePreprocessor,
|
||||||
|
StylePreprocessorResults
|
||||||
|
} from './stylePreprocessors'
|
||||||
|
|
||||||
|
export interface StyleCompileOptions {
|
||||||
|
source: string
|
||||||
|
filename: string
|
||||||
|
id: string
|
||||||
|
map?: any
|
||||||
|
scoped?: boolean
|
||||||
|
trim?: boolean
|
||||||
|
preprocessLang?: string
|
||||||
|
preprocessOptions?: any
|
||||||
|
postcssOptions?: any
|
||||||
|
postcssPlugins?: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AsyncStyleCompileOptions extends StyleCompileOptions {
|
||||||
|
isAsync?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StyleCompileResults {
|
||||||
|
code: string
|
||||||
|
map: any | void
|
||||||
|
rawResult: LazyResult | void
|
||||||
|
errors: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileStyle(
|
||||||
|
options: StyleCompileOptions
|
||||||
|
): StyleCompileResults {
|
||||||
|
return doCompileStyle({ ...options, isAsync: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileStyleAsync(
|
||||||
|
options: StyleCompileOptions
|
||||||
|
): Promise<StyleCompileResults> {
|
||||||
|
return Promise.resolve(doCompileStyle({ ...options, isAsync: true }))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doCompileStyle(
|
||||||
|
options: AsyncStyleCompileOptions
|
||||||
|
): StyleCompileResults {
|
||||||
|
const {
|
||||||
|
filename,
|
||||||
|
id,
|
||||||
|
scoped = true,
|
||||||
|
trim = true,
|
||||||
|
preprocessLang,
|
||||||
|
postcssOptions,
|
||||||
|
postcssPlugins
|
||||||
|
} = options
|
||||||
|
const preprocessor = preprocessLang && processors[preprocessLang]
|
||||||
|
const preProcessedSource = preprocessor && preprocess(options, preprocessor)
|
||||||
|
const map = preProcessedSource ? preProcessedSource.map : options.map
|
||||||
|
const source = preProcessedSource ? preProcessedSource.code : options.source
|
||||||
|
|
||||||
|
const plugins = (postcssPlugins || []).slice()
|
||||||
|
if (trim) {
|
||||||
|
plugins.push(trimPlugin())
|
||||||
|
}
|
||||||
|
if (scoped) {
|
||||||
|
plugins.push(scopedPlugin(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
const postCSSOptions: ProcessOptions = {
|
||||||
|
...postcssOptions,
|
||||||
|
to: filename,
|
||||||
|
from: filename
|
||||||
|
}
|
||||||
|
if (map) {
|
||||||
|
postCSSOptions.map = {
|
||||||
|
inline: false,
|
||||||
|
annotation: false,
|
||||||
|
prev: map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result, code, outMap
|
||||||
|
const errors: any[] = []
|
||||||
|
if (preProcessedSource && preProcessedSource.errors.length) {
|
||||||
|
errors.push(...preProcessedSource.errors)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
result = postcss(plugins).process(source, postCSSOptions)
|
||||||
|
|
||||||
|
// In async mode, return a promise.
|
||||||
|
if (options.isAsync) {
|
||||||
|
return result
|
||||||
|
.then(
|
||||||
|
(result: LazyResult): StyleCompileResults => ({
|
||||||
|
code: result.css || '',
|
||||||
|
map: result.map && result.map.toJSON(),
|
||||||
|
errors,
|
||||||
|
rawResult: result
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(
|
||||||
|
(error: Error): StyleCompileResults => ({
|
||||||
|
code: '',
|
||||||
|
map: undefined,
|
||||||
|
errors: [...errors, error.message],
|
||||||
|
rawResult: undefined
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// force synchronous transform (we know we only have sync plugins)
|
||||||
|
code = result.css
|
||||||
|
outMap = result.map
|
||||||
|
} catch (e) {
|
||||||
|
errors.push(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: code || ``,
|
||||||
|
map: outMap && outMap.toJSON(),
|
||||||
|
errors,
|
||||||
|
rawResult: result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function preprocess(
|
||||||
|
options: StyleCompileOptions,
|
||||||
|
preprocessor: StylePreprocessor
|
||||||
|
): StylePreprocessorResults {
|
||||||
|
return preprocessor(
|
||||||
|
options.source,
|
||||||
|
options.map,
|
||||||
|
Object.assign(
|
||||||
|
{
|
||||||
|
filename: options.filename
|
||||||
|
},
|
||||||
|
options.preprocessOptions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
203
packages/compiler-sfc/src/compileTemplate.ts
Normal file
203
packages/compiler-sfc/src/compileTemplate.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import {
|
||||||
|
VueTemplateCompiler,
|
||||||
|
VueTemplateCompilerOptions,
|
||||||
|
ErrorWithRange
|
||||||
|
} from './types'
|
||||||
|
import assetUrlsModule, {
|
||||||
|
AssetURLOptions,
|
||||||
|
TransformAssetUrlsOptions
|
||||||
|
} from './templateCompilerModules/assetUrl'
|
||||||
|
import srcsetModule from './templateCompilerModules/srcset'
|
||||||
|
import consolidate from '@vue/consolidate'
|
||||||
|
import * as _compiler from 'web/entry-compiler'
|
||||||
|
import transpile from 'vue-template-es2015-compiler'
|
||||||
|
|
||||||
|
export interface TemplateCompileOptions {
|
||||||
|
source: string
|
||||||
|
filename: string
|
||||||
|
compiler?: VueTemplateCompiler
|
||||||
|
compilerOptions?: VueTemplateCompilerOptions
|
||||||
|
transformAssetUrls?: AssetURLOptions | boolean
|
||||||
|
transformAssetUrlsOptions?: TransformAssetUrlsOptions
|
||||||
|
preprocessLang?: string
|
||||||
|
preprocessOptions?: any
|
||||||
|
transpileOptions?: any
|
||||||
|
isProduction?: boolean
|
||||||
|
isFunctional?: boolean
|
||||||
|
optimizeSSR?: boolean
|
||||||
|
prettify?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TemplateCompileResult {
|
||||||
|
ast: Object | undefined
|
||||||
|
code: string
|
||||||
|
source: string
|
||||||
|
tips: (string | ErrorWithRange)[]
|
||||||
|
errors: (string | ErrorWithRange)[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileTemplate(
|
||||||
|
options: TemplateCompileOptions
|
||||||
|
): TemplateCompileResult {
|
||||||
|
const { preprocessLang } = options
|
||||||
|
const preprocessor = preprocessLang && consolidate[preprocessLang]
|
||||||
|
if (preprocessor) {
|
||||||
|
return actuallyCompile(
|
||||||
|
Object.assign({}, options, {
|
||||||
|
source: preprocess(options, preprocessor)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else if (preprocessLang) {
|
||||||
|
return {
|
||||||
|
ast: {},
|
||||||
|
code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
|
||||||
|
source: options.source,
|
||||||
|
tips: [
|
||||||
|
`Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
|
||||||
|
],
|
||||||
|
errors: [
|
||||||
|
`Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return actuallyCompile(options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function preprocess(
|
||||||
|
options: TemplateCompileOptions,
|
||||||
|
preprocessor: any
|
||||||
|
): string {
|
||||||
|
const { source, filename, preprocessOptions } = options
|
||||||
|
|
||||||
|
const finalPreprocessOptions = Object.assign(
|
||||||
|
{
|
||||||
|
filename
|
||||||
|
},
|
||||||
|
preprocessOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Consolidate exposes a callback based API, but the callback is in fact
|
||||||
|
// called synchronously for most templating engines. In our case, we have to
|
||||||
|
// expose a synchronous API so that it is usable in Jest transforms (which
|
||||||
|
// have to be sync because they are applied via Node.js require hooks)
|
||||||
|
let res: any, err
|
||||||
|
preprocessor.render(
|
||||||
|
source,
|
||||||
|
finalPreprocessOptions,
|
||||||
|
(_err: Error | null, _res: string) => {
|
||||||
|
if (_err) err = _err
|
||||||
|
res = _res
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (err) throw err
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function actuallyCompile(
|
||||||
|
options: TemplateCompileOptions
|
||||||
|
): TemplateCompileResult {
|
||||||
|
const {
|
||||||
|
source,
|
||||||
|
compiler = _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
|
||||||
|
? assetUrlsModule(undefined, transformAssetUrlsOptions)
|
||||||
|
: assetUrlsModule(transformAssetUrls, transformAssetUrlsOptions),
|
||||||
|
srcsetModule(transformAssetUrlsOptions)
|
||||||
|
]
|
||||||
|
finalCompilerOptions = Object.assign({}, compilerOptions, {
|
||||||
|
modules: [...builtInModules, ...(compilerOptions.modules || [])],
|
||||||
|
filename: options.filename
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ast, render, staticRenderFns, tips, errors } = compile(
|
||||||
|
source,
|
||||||
|
finalCompilerOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
if (errors && errors.length) {
|
||||||
|
return {
|
||||||
|
ast,
|
||||||
|
code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
|
||||||
|
source,
|
||||||
|
tips,
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO better transpile
|
||||||
|
const finalTranspileOptions = Object.assign({}, transpileOptions, {
|
||||||
|
transforms: Object.assign({}, transpileOptions.transforms, {
|
||||||
|
stripWithFunctional: isFunctional
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const toFunction = (code: string): string => {
|
||||||
|
return `function (${isFunctional ? `_h,_vm` : ``}) {${code}}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// transpile code with vue-template-es2015-compiler, which is a forked
|
||||||
|
// version of Buble that applies ES2015 transforms + stripping `with` usage
|
||||||
|
let code =
|
||||||
|
transpile(
|
||||||
|
`var __render__ = ${toFunction(render)}\n` +
|
||||||
|
`var __staticRenderFns__ = [${staticRenderFns.map(toFunction)}]`,
|
||||||
|
finalTranspileOptions
|
||||||
|
) + `\n`
|
||||||
|
|
||||||
|
// #23 we use __render__ to avoid `render` not being prefixed by the
|
||||||
|
// transpiler when stripping with, but revert it back to `render` to
|
||||||
|
// maintain backwards compat
|
||||||
|
code = code.replace(/\s__(render|staticRenderFns)__\s/g, ' $1 ')
|
||||||
|
|
||||||
|
if (!isProduction) {
|
||||||
|
// mark with stripped (this enables Vue to use correct runtime proxy
|
||||||
|
// detection)
|
||||||
|
code += `render._withStripped = true`
|
||||||
|
|
||||||
|
if (prettify) {
|
||||||
|
try {
|
||||||
|
code = require('prettier').format(code, {
|
||||||
|
semi: false,
|
||||||
|
parser: 'babel'
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.code === 'MODULE_NOT_FOUND') {
|
||||||
|
tips.push(
|
||||||
|
'The `prettify` option is on, but the dependency `prettier` is not found.\n' +
|
||||||
|
'Please either turn off `prettify` or manually install `prettier`.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tips.push(
|
||||||
|
`Failed to prettify component ${options.filename} template source after compilation.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ast,
|
||||||
|
code,
|
||||||
|
source,
|
||||||
|
tips,
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
packages/compiler-sfc/src/index.ts
Normal file
12
packages/compiler-sfc/src/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// API
|
||||||
|
export { parse } from './parse'
|
||||||
|
export { compileTemplate } from './compileTemplate'
|
||||||
|
export { compileStyle, compileStyleAsync } from './compileStyle'
|
||||||
|
|
||||||
|
// types
|
||||||
|
export { SFCBlock, SFCCustomBlock, SFCDescriptor } from './parseComponent'
|
||||||
|
export {
|
||||||
|
TemplateCompileOptions,
|
||||||
|
TemplateCompileResult
|
||||||
|
} from './compileTemplate'
|
||||||
|
export { StyleCompileOptions, StyleCompileResults } from './compileStyle'
|
112
packages/compiler-sfc/src/parse.ts
Normal file
112
packages/compiler-sfc/src/parse.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { SourceMapGenerator } from 'source-map'
|
||||||
|
import { RawSourceMap, VueTemplateCompiler } from './types'
|
||||||
|
import {
|
||||||
|
parseComponent,
|
||||||
|
VueTemplateCompilerParseOptions,
|
||||||
|
SFCDescriptor
|
||||||
|
} from './parseComponent'
|
||||||
|
|
||||||
|
import hash from 'hash-sum'
|
||||||
|
import LRU from 'lru-cache'
|
||||||
|
|
||||||
|
const cache = new LRU<string, SFCDescriptor>(100)
|
||||||
|
|
||||||
|
const splitRE = /\r?\n/g
|
||||||
|
const emptyRE = /^(?:\/\/)?\s*$/
|
||||||
|
|
||||||
|
export interface ParseOptions {
|
||||||
|
source: string
|
||||||
|
filename?: string
|
||||||
|
compiler?: VueTemplateCompiler
|
||||||
|
compilerParseOptions?: VueTemplateCompilerParseOptions
|
||||||
|
sourceRoot?: string
|
||||||
|
needMap?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parse(options: ParseOptions): SFCDescriptor {
|
||||||
|
const {
|
||||||
|
source,
|
||||||
|
filename = '',
|
||||||
|
compiler,
|
||||||
|
compilerParseOptions = { pad: 'line' } as VueTemplateCompilerParseOptions,
|
||||||
|
sourceRoot = '',
|
||||||
|
needMap = true
|
||||||
|
} = options
|
||||||
|
const cacheKey = hash(
|
||||||
|
filename + source + JSON.stringify(compilerParseOptions)
|
||||||
|
)
|
||||||
|
|
||||||
|
let output = cache.get(cacheKey)
|
||||||
|
if (output) {
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compiler) {
|
||||||
|
// user-provided compiler
|
||||||
|
output = compiler.parseComponent(source, compilerParseOptions)
|
||||||
|
} else {
|
||||||
|
// use built-in compiler
|
||||||
|
output = 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
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSourceMap(
|
||||||
|
filename: string,
|
||||||
|
source: string,
|
||||||
|
generated: string,
|
||||||
|
sourceRoot: string,
|
||||||
|
pad?: 'line' | 'space' | boolean
|
||||||
|
): RawSourceMap {
|
||||||
|
const map = new SourceMapGenerator({
|
||||||
|
file: filename.replace(/\\/g, '/'),
|
||||||
|
sourceRoot: sourceRoot.replace(/\\/g, '/')
|
||||||
|
})
|
||||||
|
let offset = 0
|
||||||
|
if (!pad) {
|
||||||
|
offset = source.split(generated).shift()!.split(splitRE).length - 1
|
||||||
|
}
|
||||||
|
map.setSourceContent(filename, source)
|
||||||
|
generated.split(splitRE).forEach((line, index) => {
|
||||||
|
if (!emptyRE.test(line)) {
|
||||||
|
map.addMapping({
|
||||||
|
source: filename,
|
||||||
|
original: {
|
||||||
|
line: index + 1 + offset,
|
||||||
|
column: 0
|
||||||
|
},
|
||||||
|
generated: {
|
||||||
|
line: index + 1,
|
||||||
|
column: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return JSON.parse(map.toString())
|
||||||
|
}
|
@ -1,23 +1,49 @@
|
|||||||
import deindent from 'de-indent'
|
import deindent from 'de-indent'
|
||||||
import { parseHTML } from 'compiler/parser/html-parser'
|
import { parseHTML } from 'compiler/parser/html-parser'
|
||||||
import { makeMap } from 'shared/util'
|
import { makeMap } from 'shared/util'
|
||||||
import {
|
import { ASTAttr, WarningMessage } from 'types/compiler'
|
||||||
ASTAttr,
|
import { RawSourceMap } from './types'
|
||||||
SFCBlock,
|
|
||||||
SFCDescriptor,
|
|
||||||
WarningMessage
|
|
||||||
} from 'types/compiler'
|
|
||||||
|
|
||||||
const splitRE = /\r?\n/g
|
const splitRE = /\r?\n/g
|
||||||
const replaceRE = /./g
|
const replaceRE = /./g
|
||||||
const isSpecialTag = makeMap('script,style,template', true)
|
const isSpecialTag = makeMap('script,style,template', true)
|
||||||
|
|
||||||
|
export interface SFCCustomBlock {
|
||||||
|
type: string
|
||||||
|
content: string
|
||||||
|
attrs: { [key: string]: string | true }
|
||||||
|
start: number
|
||||||
|
end?: number
|
||||||
|
map?: RawSourceMap
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SFCBlock extends SFCCustomBlock {
|
||||||
|
lang?: string
|
||||||
|
src?: string
|
||||||
|
scoped?: boolean
|
||||||
|
module?: string | boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SFCDescriptor {
|
||||||
|
template: SFCBlock | null
|
||||||
|
script: SFCBlock | null
|
||||||
|
styles: SFCBlock[]
|
||||||
|
customBlocks: SFCCustomBlock[]
|
||||||
|
errors: WarningMessage[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VueTemplateCompilerParseOptions {
|
||||||
|
pad?: 'line' | 'space' | boolean
|
||||||
|
deindent?: boolean
|
||||||
|
outputSourceRange?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a single-file component (*.vue) file into an SFC Descriptor Object.
|
* Parse a single-file component (*.vue) file into an SFC Descriptor Object.
|
||||||
*/
|
*/
|
||||||
export function parseComponent(
|
export function parseComponent(
|
||||||
content: string,
|
content: string,
|
||||||
options: Record<string, any> = {}
|
options: VueTemplateCompilerParseOptions = {}
|
||||||
): SFCDescriptor {
|
): SFCDescriptor {
|
||||||
const sfc: SFCDescriptor = {
|
const sfc: SFCDescriptor = {
|
||||||
template: null,
|
template: null,
|
||||||
@ -48,7 +74,7 @@ export function parseComponent(
|
|||||||
|
|
||||||
function start(
|
function start(
|
||||||
tag: string,
|
tag: string,
|
||||||
attrs: Array<ASTAttr>,
|
attrs: ASTAttr[],
|
||||||
unary: boolean,
|
unary: boolean,
|
||||||
start: number,
|
start: number,
|
||||||
end: number
|
end: number
|
||||||
@ -80,7 +106,7 @@ export function parseComponent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkAttrs(block: SFCBlock, attrs: Array<ASTAttr>) {
|
function checkAttrs(block: SFCBlock, attrs: ASTAttr[]) {
|
||||||
for (let i = 0; i < attrs.length; i++) {
|
for (let i = 0; i < attrs.length; i++) {
|
||||||
const attr = attrs[i]
|
const attr = attrs[i]
|
||||||
if (attr.name === 'lang') {
|
if (attr.name === 'lang') {
|
207
packages/compiler-sfc/src/stylePlugins/scoped.ts
Normal file
207
packages/compiler-sfc/src/stylePlugins/scoped.ts
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import { PluginCreator, Rule, AtRule } from 'postcss'
|
||||||
|
import selectorParser from 'postcss-selector-parser'
|
||||||
|
import { warn } from '../warn'
|
||||||
|
|
||||||
|
const animationNameRE = /^(-\w+-)?animation-name$/
|
||||||
|
const animationRE = /^(-\w+-)?animation$/
|
||||||
|
|
||||||
|
const scopedPlugin: PluginCreator<string> = (id = '') => {
|
||||||
|
const keyframes = Object.create(null)
|
||||||
|
const shortId = id.replace(/^data-v-/, '')
|
||||||
|
|
||||||
|
return {
|
||||||
|
postcssPlugin: 'vue-sfc-scoped',
|
||||||
|
Rule(rule) {
|
||||||
|
processRule(id, rule)
|
||||||
|
},
|
||||||
|
AtRule(node) {
|
||||||
|
if (
|
||||||
|
/-?keyframes$/.test(node.name) &&
|
||||||
|
!node.params.endsWith(`-${shortId}`)
|
||||||
|
) {
|
||||||
|
// register keyframes
|
||||||
|
keyframes[node.params] = node.params = node.params + '-' + shortId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnceExit(root) {
|
||||||
|
if (Object.keys(keyframes).length) {
|
||||||
|
// If keyframes are found in this <style>, find and rewrite animation names
|
||||||
|
// in declarations.
|
||||||
|
// Caveat: this only works for keyframes and animation rules in the same
|
||||||
|
// <style> element.
|
||||||
|
// individual animation-name declaration
|
||||||
|
root.walkDecls(decl => {
|
||||||
|
if (animationNameRE.test(decl.prop)) {
|
||||||
|
decl.value = decl.value
|
||||||
|
.split(',')
|
||||||
|
.map(v => keyframes[v.trim()] || v.trim())
|
||||||
|
.join(',')
|
||||||
|
}
|
||||||
|
// shorthand
|
||||||
|
if (animationRE.test(decl.prop)) {
|
||||||
|
decl.value = decl.value
|
||||||
|
.split(',')
|
||||||
|
.map(v => {
|
||||||
|
const vals = v.trim().split(/\s+/)
|
||||||
|
const i = vals.findIndex(val => keyframes[val])
|
||||||
|
if (i !== -1) {
|
||||||
|
vals.splice(i, 1, keyframes[vals[i]])
|
||||||
|
return vals.join(' ')
|
||||||
|
} else {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(',')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processedRules = new WeakSet<Rule>()
|
||||||
|
|
||||||
|
function processRule(id: string, rule: Rule) {
|
||||||
|
if (
|
||||||
|
processedRules.has(rule) ||
|
||||||
|
(rule.parent &&
|
||||||
|
rule.parent.type === 'atrule' &&
|
||||||
|
/-?keyframes$/.test((rule.parent as AtRule).name))
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
processedRules.add(rule)
|
||||||
|
rule.selector = selectorParser(selectorRoot => {
|
||||||
|
selectorRoot.each(selector => {
|
||||||
|
rewriteSelector(id, selector, selectorRoot)
|
||||||
|
})
|
||||||
|
}).processSync(rule.selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteSelector(
|
||||||
|
id: string,
|
||||||
|
selector: selectorParser.Selector,
|
||||||
|
selectorRoot: selectorParser.Root,
|
||||||
|
slotted = false
|
||||||
|
) {
|
||||||
|
let node: selectorParser.Node | null = null
|
||||||
|
let shouldInject = true
|
||||||
|
// find the last child node to insert attribute selector
|
||||||
|
selector.each(n => {
|
||||||
|
// DEPRECATED ">>>" and "/deep/" combinator
|
||||||
|
if (
|
||||||
|
n.type === 'combinator' &&
|
||||||
|
(n.value === '>>>' || n.value === '/deep/')
|
||||||
|
) {
|
||||||
|
n.value = ' '
|
||||||
|
n.spaces.before = n.spaces.after = ''
|
||||||
|
warn(
|
||||||
|
`the >>> and /deep/ combinators have been deprecated. ` +
|
||||||
|
`Use :deep() instead.`
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n.type === 'pseudo') {
|
||||||
|
const { value } = n
|
||||||
|
// deep: inject [id] attribute at the node before the ::v-deep
|
||||||
|
// combinator.
|
||||||
|
if (value === ':deep' || value === '::v-deep') {
|
||||||
|
if (n.nodes.length) {
|
||||||
|
// .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
|
||||||
|
// replace the current node with ::v-deep's inner selector
|
||||||
|
let last: selectorParser.Selector['nodes'][0] = n
|
||||||
|
n.nodes[0].each(ss => {
|
||||||
|
selector.insertAfter(last, ss)
|
||||||
|
last = ss
|
||||||
|
})
|
||||||
|
// insert a space combinator before if it doesn't already have one
|
||||||
|
const prev = selector.at(selector.index(n) - 1)
|
||||||
|
if (!prev || !isSpaceCombinator(prev)) {
|
||||||
|
selector.insertAfter(
|
||||||
|
n,
|
||||||
|
selectorParser.combinator({
|
||||||
|
value: ' '
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
selector.removeChild(n)
|
||||||
|
} else {
|
||||||
|
// DEPRECATED usage
|
||||||
|
// .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
|
||||||
|
warn(
|
||||||
|
`::v-deep usage as a combinator has ` +
|
||||||
|
`been deprecated. Use :deep(<inner-selector>) instead.`
|
||||||
|
)
|
||||||
|
const prev = selector.at(selector.index(n) - 1)
|
||||||
|
if (prev && isSpaceCombinator(prev)) {
|
||||||
|
selector.removeChild(prev)
|
||||||
|
}
|
||||||
|
selector.removeChild(n)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// slot: use selector inside `::v-slotted` and inject [id + '-s']
|
||||||
|
// instead.
|
||||||
|
// ::v-slotted(.foo) -> .foo[xxxxxxx-s]
|
||||||
|
if (value === ':slotted' || value === '::v-slotted') {
|
||||||
|
rewriteSelector(id, n.nodes[0], selectorRoot, true /* slotted */)
|
||||||
|
let last: selectorParser.Selector['nodes'][0] = n
|
||||||
|
n.nodes[0].each(ss => {
|
||||||
|
selector.insertAfter(last, ss)
|
||||||
|
last = ss
|
||||||
|
})
|
||||||
|
// selector.insertAfter(n, n.nodes[0])
|
||||||
|
selector.removeChild(n)
|
||||||
|
// since slotted attribute already scopes the selector there's no
|
||||||
|
// need for the non-slot attribute.
|
||||||
|
shouldInject = false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// global: replace with inner selector and do not inject [id].
|
||||||
|
// ::v-global(.foo) -> .foo
|
||||||
|
if (value === ':global' || value === '::v-global') {
|
||||||
|
selectorRoot.insertAfter(selector, n.nodes[0])
|
||||||
|
selectorRoot.removeChild(selector)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n.type !== 'pseudo' && n.type !== 'combinator') {
|
||||||
|
node = n
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
;(node as selectorParser.Node).spaces.after = ''
|
||||||
|
} else {
|
||||||
|
// For deep selectors & standalone pseudo selectors,
|
||||||
|
// the attribute selectors are prepended rather than appended.
|
||||||
|
// So all leading spaces must be eliminated to avoid problems.
|
||||||
|
selector.first.spaces.before = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldInject) {
|
||||||
|
const idToAdd = slotted ? id + '-s' : id
|
||||||
|
selector.insertAfter(
|
||||||
|
// If node is null it means we need to inject [id] at the start
|
||||||
|
// insertAfter can handle `null` here
|
||||||
|
node as any,
|
||||||
|
selectorParser.attribute({
|
||||||
|
attribute: idToAdd,
|
||||||
|
value: idToAdd,
|
||||||
|
raws: {},
|
||||||
|
quoteMark: `"`
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSpaceCombinator(node: selectorParser.Node) {
|
||||||
|
return node.type === 'combinator' && /^\s+$/.test(node.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
scopedPlugin.postcss = true
|
||||||
|
export default scopedPlugin
|
18
packages/compiler-sfc/src/stylePlugins/trim.ts
Normal file
18
packages/compiler-sfc/src/stylePlugins/trim.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { PluginCreator } from 'postcss'
|
||||||
|
|
||||||
|
const trimPlugin: PluginCreator<{}> = () => {
|
||||||
|
return {
|
||||||
|
postcssPlugin: 'vue-sfc-trim',
|
||||||
|
Once(root) {
|
||||||
|
root.walk(({ type, raws }) => {
|
||||||
|
if (type === 'rule' || type === 'atrule') {
|
||||||
|
if (raws.before) raws.before = '\n'
|
||||||
|
if ('after' in raws && raws.after) raws.after = '\n'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trimPlugin.postcss = true
|
||||||
|
export default trimPlugin
|
135
packages/compiler-sfc/src/stylePreprocessors.ts
Normal file
135
packages/compiler-sfc/src/stylePreprocessors.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import merge from 'merge-source-map'
|
||||||
|
import { RawSourceMap } from 'source-map'
|
||||||
|
import { isFunction } from 'shared/util'
|
||||||
|
|
||||||
|
export type StylePreprocessor = (
|
||||||
|
source: string,
|
||||||
|
map: RawSourceMap | undefined,
|
||||||
|
options: {
|
||||||
|
[key: string]: any
|
||||||
|
additionalData?: string | ((source: string, filename: string) => string)
|
||||||
|
filename: string
|
||||||
|
}
|
||||||
|
) => StylePreprocessorResults
|
||||||
|
|
||||||
|
export interface StylePreprocessorResults {
|
||||||
|
code: string
|
||||||
|
map?: object
|
||||||
|
errors: Error[]
|
||||||
|
dependencies: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// .scss/.sass processor
|
||||||
|
const scss: StylePreprocessor = (source, map, options) => {
|
||||||
|
const nodeSass = require('sass')
|
||||||
|
const finalOptions = {
|
||||||
|
...options,
|
||||||
|
data: getSource(source, options.filename, options.additionalData),
|
||||||
|
file: options.filename,
|
||||||
|
outFile: options.filename,
|
||||||
|
sourceMap: !!map
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = nodeSass.renderSync(finalOptions)
|
||||||
|
const dependencies = result.stats.includedFiles
|
||||||
|
if (map) {
|
||||||
|
return {
|
||||||
|
code: result.css.toString(),
|
||||||
|
map: merge(map, JSON.parse(result.map.toString())),
|
||||||
|
errors: [],
|
||||||
|
dependencies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { code: result.css.toString(), errors: [], dependencies }
|
||||||
|
} catch (e: any) {
|
||||||
|
return { code: '', errors: [e], dependencies: [] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sass: StylePreprocessor = (source, map, options) =>
|
||||||
|
scss(source, map, {
|
||||||
|
...options,
|
||||||
|
indentedSyntax: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// .less
|
||||||
|
const less: StylePreprocessor = (source, map, options) => {
|
||||||
|
const nodeLess = require('less')
|
||||||
|
|
||||||
|
let result: any
|
||||||
|
let error: Error | null = null
|
||||||
|
nodeLess.render(
|
||||||
|
getSource(source, options.filename, options.additionalData),
|
||||||
|
{ ...options, syncImport: true },
|
||||||
|
(err: Error | null, output: any) => {
|
||||||
|
error = err
|
||||||
|
result = output
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (error) return { code: '', errors: [error], dependencies: [] }
|
||||||
|
const dependencies = result.imports
|
||||||
|
if (map) {
|
||||||
|
return {
|
||||||
|
code: result.css.toString(),
|
||||||
|
map: merge(map, result.map),
|
||||||
|
errors: [],
|
||||||
|
dependencies: dependencies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: result.css.toString(),
|
||||||
|
errors: [],
|
||||||
|
dependencies: dependencies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .styl
|
||||||
|
const styl: StylePreprocessor = (source, map, options) => {
|
||||||
|
const nodeStylus = require('stylus')
|
||||||
|
try {
|
||||||
|
const ref = nodeStylus(source)
|
||||||
|
Object.keys(options).forEach(key => ref.set(key, options[key]))
|
||||||
|
if (map) ref.set('sourcemap', { inline: false, comment: false })
|
||||||
|
|
||||||
|
const result = ref.render()
|
||||||
|
const dependencies = ref.deps()
|
||||||
|
if (map) {
|
||||||
|
return {
|
||||||
|
code: result,
|
||||||
|
map: merge(map, ref.sourcemap),
|
||||||
|
errors: [],
|
||||||
|
dependencies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { code: result, errors: [], dependencies }
|
||||||
|
} catch (e: any) {
|
||||||
|
return { code: '', errors: [e], dependencies: [] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSource(
|
||||||
|
source: string,
|
||||||
|
filename: string,
|
||||||
|
additionalData?: string | ((source: string, filename: string) => string)
|
||||||
|
) {
|
||||||
|
if (!additionalData) return source
|
||||||
|
if (isFunction(additionalData)) {
|
||||||
|
return additionalData(source, filename)
|
||||||
|
}
|
||||||
|
return additionalData + source
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PreprocessLang = 'less' | 'sass' | 'scss' | 'styl' | 'stylus'
|
||||||
|
|
||||||
|
export const processors: Record<PreprocessLang, StylePreprocessor> = {
|
||||||
|
less,
|
||||||
|
sass,
|
||||||
|
scss,
|
||||||
|
styl,
|
||||||
|
stylus: styl
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
// vue compiler module for transforming `<tag>:<attribute>` to `require`
|
||||||
|
|
||||||
|
import { urlToRequire, ASTNode, Attr } from './utils'
|
||||||
|
|
||||||
|
export interface AssetURLOptions {
|
||||||
|
[name: string]: string | string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransformAssetUrlsOptions {
|
||||||
|
/**
|
||||||
|
* If base is provided, instead of transforming relative asset urls into
|
||||||
|
* imports, they will be directly rewritten to absolute urls.
|
||||||
|
*/
|
||||||
|
base?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: AssetURLOptions = {
|
||||||
|
audio: 'src',
|
||||||
|
video: ['src', 'poster'],
|
||||||
|
source: 'src',
|
||||||
|
img: 'src',
|
||||||
|
image: ['xlink:href', 'href'],
|
||||||
|
use: ['xlink:href', 'href']
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (
|
||||||
|
userOptions?: AssetURLOptions,
|
||||||
|
transformAssetUrlsOption?: TransformAssetUrlsOptions
|
||||||
|
) => {
|
||||||
|
const options = userOptions
|
||||||
|
? Object.assign({}, defaultOptions, userOptions)
|
||||||
|
: defaultOptions
|
||||||
|
|
||||||
|
return {
|
||||||
|
postTransformNode: (node: ASTNode) => {
|
||||||
|
transform(node, options, transformAssetUrlsOption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function transform(
|
||||||
|
node: ASTNode,
|
||||||
|
options: AssetURLOptions,
|
||||||
|
transformAssetUrlsOption?: TransformAssetUrlsOptions
|
||||||
|
) {
|
||||||
|
for (const tag in options) {
|
||||||
|
if ((tag === '*' || node.tag === tag) && node.attrs) {
|
||||||
|
const attributes = options[tag]
|
||||||
|
if (typeof attributes === 'string') {
|
||||||
|
node.attrs.some(attr =>
|
||||||
|
rewrite(attr, attributes, transformAssetUrlsOption)
|
||||||
|
)
|
||||||
|
} else if (Array.isArray(attributes)) {
|
||||||
|
attributes.forEach(item =>
|
||||||
|
node.attrs.some(attr => rewrite(attr, item, transformAssetUrlsOption))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewrite(
|
||||||
|
attr: Attr,
|
||||||
|
name: string,
|
||||||
|
transformAssetUrlsOption?: TransformAssetUrlsOptions
|
||||||
|
) {
|
||||||
|
if (attr.name === name) {
|
||||||
|
const value = attr.value
|
||||||
|
// only transform static URLs
|
||||||
|
if (value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
|
||||||
|
attr.value = urlToRequire(value.slice(1, -1), transformAssetUrlsOption)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
73
packages/compiler-sfc/src/templateCompilerModules/srcset.ts
Normal file
73
packages/compiler-sfc/src/templateCompilerModules/srcset.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// vue compiler module for transforming `img:srcset` to a number of `require`s
|
||||||
|
|
||||||
|
import { urlToRequire, ASTNode } from './utils'
|
||||||
|
import { TransformAssetUrlsOptions } from './assetUrl'
|
||||||
|
|
||||||
|
interface ImageCandidate {
|
||||||
|
require: string
|
||||||
|
descriptor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (transformAssetUrlsOptions?: TransformAssetUrlsOptions) => ({
|
||||||
|
postTransformNode: (node: ASTNode) => {
|
||||||
|
transform(node, transformAssetUrlsOptions)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// http://w3c.github.io/html/semantics-embedded-content.html#ref-for-image-candidate-string-5
|
||||||
|
const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g
|
||||||
|
|
||||||
|
function transform(
|
||||||
|
node: ASTNode,
|
||||||
|
transformAssetUrlsOptions?: TransformAssetUrlsOptions
|
||||||
|
) {
|
||||||
|
const tags = ['img', 'source']
|
||||||
|
|
||||||
|
if (tags.indexOf(node.tag) !== -1 && node.attrs) {
|
||||||
|
node.attrs.forEach(attr => {
|
||||||
|
if (attr.name === 'srcset') {
|
||||||
|
// same logic as in transform-require.js
|
||||||
|
const value = attr.value
|
||||||
|
const isStatic =
|
||||||
|
value.charAt(0) === '"' && value.charAt(value.length - 1) === '"'
|
||||||
|
if (!isStatic) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageCandidates: ImageCandidate[] = value
|
||||||
|
.substr(1, value.length - 2)
|
||||||
|
.split(',')
|
||||||
|
.map(s => {
|
||||||
|
// The attribute value arrives here with all whitespace, except
|
||||||
|
// normal spaces, represented by escape sequences
|
||||||
|
const [url, descriptor] = s
|
||||||
|
.replace(escapedSpaceCharacters, ' ')
|
||||||
|
.trim()
|
||||||
|
.split(' ', 2)
|
||||||
|
return {
|
||||||
|
require: urlToRequire(url, transformAssetUrlsOptions),
|
||||||
|
descriptor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// "require(url1)"
|
||||||
|
// "require(url1) 1x"
|
||||||
|
// "require(url1), require(url2)"
|
||||||
|
// "require(url1), require(url2) 2x"
|
||||||
|
// "require(url1) 1x, require(url2)"
|
||||||
|
// "require(url1) 1x, require(url2) 2x"
|
||||||
|
const code = imageCandidates
|
||||||
|
.map(
|
||||||
|
({ require, descriptor }) =>
|
||||||
|
`${require} + "${descriptor ? ' ' + descriptor : ''}, " + `
|
||||||
|
)
|
||||||
|
.join('')
|
||||||
|
.slice(0, -6)
|
||||||
|
.concat('"')
|
||||||
|
.replace(/ \+ ""$/, '')
|
||||||
|
|
||||||
|
attr.value = code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
74
packages/compiler-sfc/src/templateCompilerModules/utils.ts
Normal file
74
packages/compiler-sfc/src/templateCompilerModules/utils.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { TransformAssetUrlsOptions } from './assetUrl'
|
||||||
|
import { UrlWithStringQuery, parse as uriParse } from 'url'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export interface Attr {
|
||||||
|
name: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ASTNode {
|
||||||
|
tag: string
|
||||||
|
attrs: Attr[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function urlToRequire(
|
||||||
|
url: string,
|
||||||
|
transformAssetUrlsOption: TransformAssetUrlsOptions = {}
|
||||||
|
): string {
|
||||||
|
const returnValue = `"${url}"`
|
||||||
|
// same logic as in transform-require.js
|
||||||
|
const firstChar = url.charAt(0)
|
||||||
|
if (firstChar === '~') {
|
||||||
|
const secondChar = url.charAt(1)
|
||||||
|
url = url.slice(secondChar === '/' ? 2 : 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const uriParts = parseUriParts(url)
|
||||||
|
|
||||||
|
if (transformAssetUrlsOption.base) {
|
||||||
|
// explicit base - directly rewrite the url into absolute url
|
||||||
|
// does not apply to absolute urls or urls that start with `@`
|
||||||
|
// since they are aliases
|
||||||
|
if (firstChar === '.' || firstChar === '~') {
|
||||||
|
// when packaged in the browser, path will be using the posix-
|
||||||
|
// only version provided by rollup-plugin-node-builtins.
|
||||||
|
return `"${(path.posix || path).join(
|
||||||
|
transformAssetUrlsOption.base,
|
||||||
|
uriParts.path + (uriParts.hash || '')
|
||||||
|
)}"`
|
||||||
|
}
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstChar === '.' || firstChar === '~' || firstChar === '@') {
|
||||||
|
if (!uriParts.hash) {
|
||||||
|
return `require("${url}")`
|
||||||
|
} else {
|
||||||
|
// support uri fragment case by excluding it from
|
||||||
|
// the require and instead appending it as string;
|
||||||
|
// assuming that the path part is sufficient according to
|
||||||
|
// the above caseing(t.i. no protocol-auth-host parts expected)
|
||||||
|
return `require("${uriParts.path}") + "${uriParts.hash}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vuejs/component-compiler-utils#22 Support uri fragment in transformed require
|
||||||
|
* @param urlString an url as a string
|
||||||
|
*/
|
||||||
|
function parseUriParts(urlString: string): UrlWithStringQuery {
|
||||||
|
// initialize return value
|
||||||
|
const returnValue: UrlWithStringQuery = uriParse('')
|
||||||
|
if (urlString) {
|
||||||
|
// A TypeError is thrown if urlString is not a string
|
||||||
|
// @see https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost
|
||||||
|
if ('string' === typeof urlString) {
|
||||||
|
// check is an uri
|
||||||
|
return uriParse(urlString) // take apart the uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnValue
|
||||||
|
}
|
52
packages/compiler-sfc/src/types.ts
Normal file
52
packages/compiler-sfc/src/types.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { SFCDescriptor } from './parseComponent'
|
||||||
|
|
||||||
|
export interface StartOfSourceMap {
|
||||||
|
file?: string
|
||||||
|
sourceRoot?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RawSourceMap extends StartOfSourceMap {
|
||||||
|
version: string
|
||||||
|
sources: string[]
|
||||||
|
names: string[]
|
||||||
|
sourcesContent?: string[]
|
||||||
|
mappings: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VueTemplateCompiler {
|
||||||
|
parseComponent(source: string, options?: any): SFCDescriptor
|
||||||
|
|
||||||
|
compile(
|
||||||
|
template: string,
|
||||||
|
options: VueTemplateCompilerOptions
|
||||||
|
): VueTemplateCompilerResults
|
||||||
|
|
||||||
|
ssrCompile(
|
||||||
|
template: string,
|
||||||
|
options: VueTemplateCompilerOptions
|
||||||
|
): VueTemplateCompilerResults
|
||||||
|
}
|
||||||
|
|
||||||
|
// we'll just shim this much for now - in the future these types
|
||||||
|
// should come from vue-template-compiler directly, or this package should be
|
||||||
|
// part of the vue monorepo.
|
||||||
|
export interface VueTemplateCompilerOptions {
|
||||||
|
modules?: Object[]
|
||||||
|
outputSourceRange?: boolean
|
||||||
|
whitespace?: 'preserve' | 'condense'
|
||||||
|
directives?: { [key: string]: Function }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorWithRange {
|
||||||
|
msg: string
|
||||||
|
start: number
|
||||||
|
end: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VueTemplateCompilerResults {
|
||||||
|
ast: Object | undefined
|
||||||
|
render: string
|
||||||
|
staticRenderFns: string[]
|
||||||
|
errors: (string | ErrorWithRange)[]
|
||||||
|
tips: (string | ErrorWithRange)[]
|
||||||
|
}
|
16
packages/compiler-sfc/src/warn.ts
Normal file
16
packages/compiler-sfc/src/warn.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const hasWarned: Record<string, boolean> = {}
|
||||||
|
|
||||||
|
export function warnOnce(msg: string) {
|
||||||
|
const isNodeProd =
|
||||||
|
typeof process !== 'undefined' && process.env.NODE_ENV === 'production'
|
||||||
|
if (!isNodeProd && !hasWarned[msg]) {
|
||||||
|
hasWarned[msg] = true
|
||||||
|
warn(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function warn(msg: string) {
|
||||||
|
console.warn(
|
||||||
|
`\x1b[1m\x1b[33m[@vue/compiler-sfc]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`
|
||||||
|
)
|
||||||
|
}
|
203
packages/compiler-sfc/test/compileStyle.spec.ts
Normal file
203
packages/compiler-sfc/test/compileStyle.spec.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import { parse } from '../src/parse'
|
||||||
|
import { compileStyle, compileStyleAsync } from '../src/compileStyle'
|
||||||
|
|
||||||
|
test.only('preprocess less', () => {
|
||||||
|
const style = parse({
|
||||||
|
source:
|
||||||
|
'<style lang="less">\n' +
|
||||||
|
'@red: rgb(255, 0, 0);\n' +
|
||||||
|
'.color { color: @red; }\n' +
|
||||||
|
'</style>\n',
|
||||||
|
filename: 'example.vue',
|
||||||
|
needMap: true
|
||||||
|
}).styles[0]
|
||||||
|
|
||||||
|
const result = compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: style.content,
|
||||||
|
map: style.map,
|
||||||
|
scoped: false,
|
||||||
|
preprocessLang: style.lang
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
expect(result.code).toEqual(expect.stringContaining('color: #ff0000;'))
|
||||||
|
expect(result.map).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('preprocess scss', () => {
|
||||||
|
const style = parse({
|
||||||
|
source:
|
||||||
|
'<style lang="scss">\n' +
|
||||||
|
'$red: rgb(255, 0, 0);\n' +
|
||||||
|
'.color { color: $red; }\n' +
|
||||||
|
'</style>\n',
|
||||||
|
filename: 'example.vue',
|
||||||
|
needMap: true
|
||||||
|
}).styles[0]
|
||||||
|
const result = compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: style.content,
|
||||||
|
map: style.map,
|
||||||
|
scoped: false,
|
||||||
|
preprocessLang: style.lang
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
expect(result.code).toEqual(expect.stringContaining('color: red;'))
|
||||||
|
expect(result.map).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('preprocess sass', () => {
|
||||||
|
const style = parse({
|
||||||
|
source:
|
||||||
|
'<style lang="sass">\n' +
|
||||||
|
'$red: rgb(255, 0, 0)\n' +
|
||||||
|
'.color\n' +
|
||||||
|
' color: $red\n' +
|
||||||
|
'</style>\n',
|
||||||
|
filename: 'example.vue',
|
||||||
|
needMap: true
|
||||||
|
}).styles[0]
|
||||||
|
const result = compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: style.content,
|
||||||
|
map: style.map,
|
||||||
|
scoped: false,
|
||||||
|
preprocessLang: style.lang
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
expect(result.code).toEqual(expect.stringContaining('color: red;'))
|
||||||
|
expect(result.map).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('preprocess stylus', () => {
|
||||||
|
const style = parse({
|
||||||
|
source:
|
||||||
|
'<style lang="styl">\n' +
|
||||||
|
'red-color = rgb(255, 0, 0);\n' +
|
||||||
|
'.color\n' +
|
||||||
|
' color: red-color\n' +
|
||||||
|
'</style>\n',
|
||||||
|
filename: 'example.vue',
|
||||||
|
needMap: true
|
||||||
|
}).styles[0]
|
||||||
|
const result = compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: style.content,
|
||||||
|
map: style.map,
|
||||||
|
scoped: false,
|
||||||
|
preprocessLang: style.lang
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
expect(result.code).toEqual(expect.stringContaining('color: #f00;'))
|
||||||
|
expect(result.map).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('custom postcss plugin', () => {
|
||||||
|
const spy = vi.fn()
|
||||||
|
|
||||||
|
compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: '.foo { color: red }',
|
||||||
|
scoped: false,
|
||||||
|
postcssPlugins: [require('postcss').plugin('test-plugin', () => spy)()]
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('custom postcss options', () => {
|
||||||
|
const result = compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: '.foo { color: red }',
|
||||||
|
scoped: false,
|
||||||
|
postcssOptions: { random: 'foo' }
|
||||||
|
})
|
||||||
|
|
||||||
|
expect((result.rawResult as any).opts.random).toBe('foo')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('async postcss plugin in sync mode', () => {
|
||||||
|
const result = compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: '.foo { color: red }',
|
||||||
|
scoped: false,
|
||||||
|
postcssPlugins: [
|
||||||
|
require('postcss').plugin(
|
||||||
|
'test-plugin',
|
||||||
|
() => async (result: any) => result
|
||||||
|
)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('async postcss plugin', async () => {
|
||||||
|
const promise = compileStyleAsync({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: '.foo { color: red }',
|
||||||
|
scoped: false,
|
||||||
|
postcssPlugins: [
|
||||||
|
require('postcss').plugin(
|
||||||
|
'test-plugin',
|
||||||
|
() => async (result: any) => result
|
||||||
|
)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(promise instanceof Promise).toBe(true)
|
||||||
|
|
||||||
|
const result = await promise
|
||||||
|
expect(result.errors).toHaveLength(0)
|
||||||
|
expect(result.code).toEqual(expect.stringContaining('color: red'))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('media query', () => {
|
||||||
|
const result = compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
scoped: true,
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: `
|
||||||
|
@media print {
|
||||||
|
.foo {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors).toHaveLength(0)
|
||||||
|
expect(result.code).toContain(
|
||||||
|
'@media print {\n.foo[v-scope-xxx] {\n color: #000;\n}\n}'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('supports query', () => {
|
||||||
|
const result = compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
scoped: true,
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: `
|
||||||
|
@supports ( color: #000 ) {
|
||||||
|
.foo {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors).toHaveLength(0)
|
||||||
|
expect(result.code).toContain(
|
||||||
|
'@supports ( color: #000 ) {\n.foo[v-scope-xxx] {\n color: #000;\n}\n}'
|
||||||
|
)
|
||||||
|
})
|
230
packages/compiler-sfc/test/compileTemplate.spec.ts
Normal file
230
packages/compiler-sfc/test/compileTemplate.spec.ts
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import { parse } from '../src/parse'
|
||||||
|
import { SFCBlock } from '../src/parseComponent'
|
||||||
|
import { compileTemplate } from '../src/compileTemplate'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
function mockRender(code: string, mocks: Record<string, any> = {}) {
|
||||||
|
console.log(code)
|
||||||
|
const fn = new Function(
|
||||||
|
`require`,
|
||||||
|
`${code}; return { render, staticRenderFns }`
|
||||||
|
)
|
||||||
|
const vm = new Vue(
|
||||||
|
Object.assign(
|
||||||
|
{},
|
||||||
|
fn((id: string) => mocks[id])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
vm.$mount()
|
||||||
|
return (vm as any)._vnode
|
||||||
|
}
|
||||||
|
|
||||||
|
test('should work', () => {
|
||||||
|
const source = `<div><p>{{ render }}</p></div>`
|
||||||
|
|
||||||
|
const result = compileTemplate({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
expect(result.source).toBe(source)
|
||||||
|
// should expose render fns
|
||||||
|
expect(result.code).toMatch(`var render = function`)
|
||||||
|
expect(result.code).toMatch(`var staticRenderFns = []`)
|
||||||
|
// should mark with stripped
|
||||||
|
expect(result.code).toMatch(`render._withStripped = true`)
|
||||||
|
// should prefix bindings
|
||||||
|
expect(result.code).toMatch(`_vm.render`)
|
||||||
|
expect(result.ast).not.toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('preprocess pug', () => {
|
||||||
|
const template = parse({
|
||||||
|
source:
|
||||||
|
'<template lang="pug">\n' +
|
||||||
|
'body\n' +
|
||||||
|
' h1 Pug Examples\n' +
|
||||||
|
' div.container\n' +
|
||||||
|
' p Cool Pug example!\n' +
|
||||||
|
'</template>\n',
|
||||||
|
filename: 'example.vue',
|
||||||
|
needMap: true
|
||||||
|
}).template as SFCBlock
|
||||||
|
|
||||||
|
const result = compileTemplate({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: template.content,
|
||||||
|
preprocessLang: template.lang
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vuejs/component-compiler-utils#22 Support uri fragment in transformed require
|
||||||
|
*/
|
||||||
|
test('supports uri fragment in transformed require', () => {
|
||||||
|
const source = '<svg>\
|
||||||
|
<use href="~@svg/file.svg#fragment"></use>\
|
||||||
|
</svg>' //
|
||||||
|
const result = compileTemplate({
|
||||||
|
filename: 'svgparticle.html',
|
||||||
|
source: source,
|
||||||
|
transformAssetUrls: {
|
||||||
|
use: 'href'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
expect(result.code).toMatch(
|
||||||
|
/href: require\("@svg\/file.svg"\) \+ "#fragment"/
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vuejs/component-compiler-utils#22 Support uri fragment in transformed require
|
||||||
|
*/
|
||||||
|
test('when too short uri then empty require', () => {
|
||||||
|
const source = '<svg>\
|
||||||
|
<use href="~"></use>\
|
||||||
|
</svg>' //
|
||||||
|
const result = compileTemplate({
|
||||||
|
filename: 'svgparticle.html',
|
||||||
|
source: source,
|
||||||
|
transformAssetUrls: {
|
||||||
|
use: 'href'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
expect(result.code).toMatch(/href: require\(""\)/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('warn missing preprocessor', () => {
|
||||||
|
const template = parse({
|
||||||
|
source: '<template lang="unknownLang">\n' + '</template>\n',
|
||||||
|
|
||||||
|
filename: 'example.vue',
|
||||||
|
needMap: true
|
||||||
|
}).template as SFCBlock
|
||||||
|
|
||||||
|
const result = compileTemplate({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: template.content,
|
||||||
|
preprocessLang: template.lang
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.only('transform assetUrls', () => {
|
||||||
|
const source = `
|
||||||
|
<div>
|
||||||
|
<img src="./logo.png">
|
||||||
|
<img src="~fixtures/logo.png">
|
||||||
|
<img src="~/fixtures/logo.png">
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
const result = compileTemplate({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source,
|
||||||
|
transformAssetUrls: true
|
||||||
|
})
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
|
||||||
|
const vnode = mockRender(result.code, {
|
||||||
|
'./logo.png': 'a',
|
||||||
|
'fixtures/logo.png': 'b'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(vnode.children[0].data.attrs.src).toBe('a')
|
||||||
|
expect(vnode.children[2].data.attrs.src).toBe('b')
|
||||||
|
expect(vnode.children[4].data.attrs.src).toBe('b')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('transform srcset', () => {
|
||||||
|
// TODO:
|
||||||
|
const source = `
|
||||||
|
<div>
|
||||||
|
<img src="./logo.png">
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink">
|
||||||
|
<image xlink:href="./logo.png" />
|
||||||
|
</svg>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink">
|
||||||
|
<use xlink:href="./logo.png"/>
|
||||||
|
</svg>
|
||||||
|
</svg>
|
||||||
|
<img src="./logo.png" srcset="./logo.png">
|
||||||
|
<img src="./logo.png" srcset="./logo.png 2x">
|
||||||
|
<img src="./logo.png" srcset="./logo.png, ./logo.png 2x">
|
||||||
|
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png">
|
||||||
|
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x">
|
||||||
|
<img src="./logo.png" srcset="./logo.png, ./logo.png 2x, ./logo.png 3x">
|
||||||
|
<img
|
||||||
|
src="./logo.png"
|
||||||
|
srcset="
|
||||||
|
./logo.png 2x,
|
||||||
|
./logo.png 3x
|
||||||
|
">
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
const result = compileTemplate({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source,
|
||||||
|
transformAssetUrls: true
|
||||||
|
})
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
|
||||||
|
const vnode = mockRender(result.code, {
|
||||||
|
'./logo.png': 'test-url'
|
||||||
|
})
|
||||||
|
|
||||||
|
// img tag
|
||||||
|
expect(vnode.children[0].data.attrs.src).toBe('test-url')
|
||||||
|
// image tag (SVG)
|
||||||
|
expect(vnode.children[2].children[0].data.attrs['xlink:href']).toBe(
|
||||||
|
'test-url'
|
||||||
|
)
|
||||||
|
// use tag (SVG)
|
||||||
|
expect(vnode.children[4].children[0].data.attrs['xlink:href']).toBe(
|
||||||
|
'test-url'
|
||||||
|
)
|
||||||
|
|
||||||
|
// image tag with srcset
|
||||||
|
expect(vnode.children[6].data.attrs.srcset).toBe('test-url')
|
||||||
|
expect(vnode.children[8].data.attrs.srcset).toBe('test-url 2x')
|
||||||
|
// image tag with multiline srcset
|
||||||
|
expect(vnode.children[10].data.attrs.srcset).toBe('test-url, test-url 2x')
|
||||||
|
expect(vnode.children[12].data.attrs.srcset).toBe('test-url 2x, test-url')
|
||||||
|
expect(vnode.children[14].data.attrs.srcset).toBe('test-url 2x, test-url 3x')
|
||||||
|
expect(vnode.children[16].data.attrs.srcset).toBe(
|
||||||
|
'test-url, test-url 2x, test-url 3x'
|
||||||
|
)
|
||||||
|
expect(vnode.children[18].data.attrs.srcset).toBe('test-url 2x, test-url 3x')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('transform assetUrls and srcset with base option', () => {
|
||||||
|
const source = `
|
||||||
|
<div>
|
||||||
|
<img src="./logo.png">
|
||||||
|
<img src="~fixtures/logo.png">
|
||||||
|
<img src="~/fixtures/logo.png">
|
||||||
|
<img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x">
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
const result = compileTemplate({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source,
|
||||||
|
transformAssetUrls: true,
|
||||||
|
transformAssetUrlsOptions: { base: '/base/' }
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(0)
|
||||||
|
|
||||||
|
const vnode = mockRender(result.code)
|
||||||
|
expect(vnode.children[0].data.attrs.src).toBe('/base/logo.png')
|
||||||
|
expect(vnode.children[2].data.attrs.src).toBe('/base/fixtures/logo.png')
|
||||||
|
expect(vnode.children[4].data.attrs.src).toBe('/base/fixtures/logo.png')
|
||||||
|
expect(vnode.children[6].data.attrs.srcset).toBe(
|
||||||
|
'/base/logo.png 2x, /base/logo.png 3x'
|
||||||
|
)
|
||||||
|
})
|
@ -1,4 +1,5 @@
|
|||||||
import { parseComponent } from 'sfc/parser'
|
import { WarningMessage } from 'types/compiler'
|
||||||
|
import { parseComponent } from '../src/parseComponent'
|
||||||
|
|
||||||
describe('Single File Component parser', () => {
|
describe('Single File Component parser', () => {
|
||||||
it('should parse', () => {
|
it('should parse', () => {
|
137
packages/compiler-sfc/test/stylePluginScoped.spec.ts
Normal file
137
packages/compiler-sfc/test/stylePluginScoped.spec.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { compileStyle } from '../src/compileStyle'
|
||||||
|
|
||||||
|
// vue-loader/#1370
|
||||||
|
test('spaces after selector', () => {
|
||||||
|
const { code } = compileStyle({
|
||||||
|
source: `.foo , .bar { color: red; }`,
|
||||||
|
filename: 'test.css',
|
||||||
|
id: 'test'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(code).toMatch(`.foo[test], .bar[test] { color: red;`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('leading deep selector', () => {
|
||||||
|
const { code } = compileStyle({
|
||||||
|
source: `>>> .foo { color: red; }`,
|
||||||
|
filename: 'test.css',
|
||||||
|
id: 'test'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(code).toMatch(`[test] .foo { color: red;`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('scoped css', () => {
|
||||||
|
const { code: style } = compileStyle({
|
||||||
|
id: 'v-scope-xxx',
|
||||||
|
scoped: true,
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: `
|
||||||
|
.test {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
.test:after {
|
||||||
|
content: 'bye!';
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.anim {
|
||||||
|
animation: color 5s infinite, other 5s;
|
||||||
|
}
|
||||||
|
.anim-2 {
|
||||||
|
animation-name: color;
|
||||||
|
animation-duration: 5s;
|
||||||
|
}
|
||||||
|
.anim-3 {
|
||||||
|
animation: 5s color infinite, 5s other;
|
||||||
|
}
|
||||||
|
.anim-multiple {
|
||||||
|
animation: color 5s infinite, opacity 2s;
|
||||||
|
}
|
||||||
|
.anim-multiple-2 {
|
||||||
|
animation-name: color, opacity;
|
||||||
|
animation-duration: 5s, 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes color {
|
||||||
|
from { color: red; }
|
||||||
|
to { color: green; }
|
||||||
|
}
|
||||||
|
@-webkit-keyframes color {
|
||||||
|
from { color: red; }
|
||||||
|
to { color: green; }
|
||||||
|
}
|
||||||
|
@keyframes opacity {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
@-webkit-keyframes opacity {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
.foo p >>> .bar {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.foo div /deep/ .bar {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.foo span ::v-deep .bar {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(style).toContain(`.test[v-scope-xxx] {\n color: yellow;\n}`)
|
||||||
|
expect(style).toContain(`.test[v-scope-xxx]:after {\n content: \'bye!\';\n}`)
|
||||||
|
expect(style).toContain(`h1[v-scope-xxx] {\n color: green;\n}`)
|
||||||
|
// scoped keyframes
|
||||||
|
expect(style).toContain(
|
||||||
|
`.anim[v-scope-xxx] {\n animation: color-v-scope-xxx 5s infinite, other 5s;`
|
||||||
|
)
|
||||||
|
expect(style).toContain(
|
||||||
|
`.anim-2[v-scope-xxx] {\n animation-name: color-v-scope-xxx`
|
||||||
|
)
|
||||||
|
expect(style).toContain(
|
||||||
|
`.anim-3[v-scope-xxx] {\n animation: 5s color-v-scope-xxx infinite, 5s other;`
|
||||||
|
)
|
||||||
|
expect(style).toContain(`@keyframes color-v-scope-xxx {`)
|
||||||
|
expect(style).toContain(`@-webkit-keyframes color-v-scope-xxx {`)
|
||||||
|
|
||||||
|
expect(style).toContain(
|
||||||
|
`.anim-multiple[v-scope-xxx] {\n animation: color-v-scope-xxx 5s infinite,opacity-v-scope-xxx 2s;`
|
||||||
|
)
|
||||||
|
expect(style).toContain(
|
||||||
|
`.anim-multiple-2[v-scope-xxx] {\n animation-name: color-v-scope-xxx,opacity-v-scope-xxx;`
|
||||||
|
)
|
||||||
|
expect(style).toContain(`@keyframes opacity-v-scope-xxx {`)
|
||||||
|
expect(style).toContain(`@-webkit-keyframes opacity-v-scope-xxx {`)
|
||||||
|
// >>> combinator
|
||||||
|
expect(style).toContain(`.foo p[v-scope-xxx] .bar {\n color: red;\n}`)
|
||||||
|
// /deep/ alias for >>>
|
||||||
|
expect(style).toContain(`.foo div[v-scope-xxx] .bar {\n color: red;\n}`)
|
||||||
|
// ::-v-deep alias for >>>
|
||||||
|
expect(style).toContain(`.foo span[v-scope-xxx] .bar {\n color: red;\n}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('pseudo element', () => {
|
||||||
|
const { code } = compileStyle({
|
||||||
|
source: '::selection { display: none; }',
|
||||||
|
filename: 'test.css',
|
||||||
|
id: 'test'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(code).toContain('[test]::selection {')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('spaces before pseudo element', () => {
|
||||||
|
const { code } = compileStyle({
|
||||||
|
source: '.abc, ::selection { color: red; }',
|
||||||
|
filename: 'test.css',
|
||||||
|
id: 'test'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(code).toContain('.abc[test],')
|
||||||
|
expect(code).toContain('[test]::selection {')
|
||||||
|
})
|
7
packages/compiler-sfc/test/tsconfig.json
Normal file
7
packages/compiler-sfc/test/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["node", "vitest/globals"]
|
||||||
|
},
|
||||||
|
"include": ["../src", "."]
|
||||||
|
}
|
@ -24,7 +24,7 @@
|
|||||||
"url": "https://github.com/vuejs/vue/issues"
|
"url": "https://github.com/vuejs/vue/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.1.2",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"lodash.template": "^4.5.0",
|
"lodash.template": "^4.5.0",
|
||||||
|
@ -26,8 +26,8 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/vue-template-compiler#readme",
|
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/vue-template-compiler#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"he": "^1.2.0",
|
"de-indent": "^1.0.2",
|
||||||
"de-indent": "^1.0.2"
|
"he": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vue": "file:../.."
|
"vue": "file:../.."
|
||||||
|
477
pnpm-lock.yaml
477
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,12 @@ const resolve = p => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we are bundling forked consolidate.js in compiler-sfc which dynamically
|
||||||
|
// requires a ton of template engines which should be ignored.
|
||||||
|
const consolidatePath = require.resolve('@vue/consolidate/package.json', {
|
||||||
|
paths: [path.resolve(__dirname, '../packages/compiler-sfc')]
|
||||||
|
})
|
||||||
|
|
||||||
const builds = {
|
const builds = {
|
||||||
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
|
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
|
||||||
'runtime-cjs-dev': {
|
'runtime-cjs-dev': {
|
||||||
@ -202,12 +208,27 @@ const builds = {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
'compiler-sfc': {
|
'compiler-sfc': {
|
||||||
entry: resolve('web/entry-compiler-sfc.ts'),
|
entry: resolve('packages/compiler-sfc/src/index.ts'),
|
||||||
dest: resolve('packages/compiler-sfc/dist/compiler-sfc.js'),
|
dest: resolve('packages/compiler-sfc/dist/compiler-sfc.js'),
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
external: Object.keys(
|
external: Object.keys(
|
||||||
require('../packages/compiler-sfc/package.json').dependencies
|
require('../packages/compiler-sfc/package.json').dependencies
|
||||||
)
|
),
|
||||||
|
plugins: [
|
||||||
|
node({ preferBuiltins: true }),
|
||||||
|
cjs({
|
||||||
|
ignore: [
|
||||||
|
...Object.keys(require(consolidatePath).devDependencies),
|
||||||
|
'vm',
|
||||||
|
'crypto',
|
||||||
|
'react-dom/server',
|
||||||
|
'teacup/lib/express',
|
||||||
|
'arc-templates/dist/es5',
|
||||||
|
'then-pug',
|
||||||
|
'then-jade'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export function compile() {}
|
|
@ -1,4 +1,4 @@
|
|||||||
export { parseComponent } from 'sfc/parser'
|
export { parseComponent } from 'sfc/parseComponent'
|
||||||
export { compile, compileToFunctions } from './compiler/index'
|
export { compile, compileToFunctions } from './compiler/index'
|
||||||
export { ssrCompile, ssrCompileToFunctions } from 'server/compiler'
|
export { ssrCompile, ssrCompileToFunctions } from 'server/compiler'
|
||||||
export { generateCodeFrame } from 'compiler/codeframe'
|
export { generateCodeFrame } from 'compiler/codeframe'
|
||||||
|
@ -199,26 +199,3 @@ export type ASTText = {
|
|||||||
start?: number
|
start?: number
|
||||||
end?: number
|
end?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// SFC-parser related declarations
|
|
||||||
|
|
||||||
// an object format describing a single-file component
|
|
||||||
export type SFCDescriptor = {
|
|
||||||
template: SFCBlock | null
|
|
||||||
script: SFCBlock | null
|
|
||||||
styles: Array<SFCBlock>
|
|
||||||
customBlocks: Array<SFCBlock>
|
|
||||||
errors: Array<string | WarningMessage>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SFCBlock = {
|
|
||||||
type: string
|
|
||||||
content: string
|
|
||||||
attrs: { [attribute: string]: string }
|
|
||||||
start?: number
|
|
||||||
end?: number
|
|
||||||
lang?: string
|
|
||||||
src?: string
|
|
||||||
scoped?: boolean
|
|
||||||
module?: string | boolean
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user