Skip to content

Commit 83782e0

Browse files
Merge pull request #26 from Toilal/vue2-scoped
wip(vue2): add scopeId support for vue2
2 parents e46a1ab + e0d6364 commit 83782e0

14 files changed

+952
-1212
lines changed

build/vue2StyleProcessors.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* This is customized version of styleProcessors defined in @vue/component-compiler-utils to bring customRequire
3+
* support.
4+
*
5+
* https://github.com/vuejs/component-compiler-utils/blob/master/lib/styleProcessors/index.ts
6+
*/
7+
8+
const merge = require('merge-source-map')
9+
10+
export interface StylePreprocessor {
11+
render(
12+
source: string,
13+
map: any | null,
14+
options: any
15+
): StylePreprocessorResults
16+
}
17+
18+
export interface StylePreprocessorResults {
19+
code: string
20+
map?: any
21+
errors: Array<Error>
22+
}
23+
24+
const customRequire = (name: string, options?: any) => {
25+
const requireFn = options?.preprocessOptions?.customRequire
26+
if (requireFn) {
27+
return requireFn(name)
28+
}
29+
return require(name)
30+
}
31+
32+
// .scss/.sass processor
33+
const scss: StylePreprocessor = {
34+
render(
35+
source: string,
36+
map: any | null,
37+
options: any
38+
): StylePreprocessorResults {
39+
const nodeSass = customRequire('sass', options)
40+
const finalOptions = Object.assign({}, options, {
41+
data: source,
42+
file: options.filename,
43+
outFile: options.filename,
44+
sourceMap: !!map
45+
})
46+
47+
try {
48+
const result = nodeSass.renderSync(finalOptions)
49+
50+
if (map) {
51+
return {
52+
code: result.css.toString(),
53+
map: merge(map, JSON.parse(result.map.toString())),
54+
errors: []
55+
}
56+
}
57+
58+
return { code: result.css.toString(), errors: [] }
59+
} catch (e) {
60+
return { code: '', errors: [e] }
61+
}
62+
}
63+
}
64+
65+
const sass = {
66+
render(
67+
source: string,
68+
map: any | null,
69+
options: any,
70+
): StylePreprocessorResults {
71+
return scss.render(
72+
source,
73+
map,
74+
Object.assign({}, options, { indentedSyntax: true })
75+
)
76+
}
77+
}
78+
79+
// .less
80+
const less = {
81+
render(
82+
source: string,
83+
map: any | null,
84+
options: any
85+
): StylePreprocessorResults {
86+
const nodeLess = customRequire('less', options)
87+
88+
let result: any
89+
let error: Error | null = null
90+
nodeLess.render(
91+
source,
92+
Object.assign({}, options, { syncImport: true }),
93+
(err: Error | null, output: any) => {
94+
error = err
95+
result = output
96+
}
97+
)
98+
99+
if (error) return { code: '', errors: [error] }
100+
101+
if (map) {
102+
return {
103+
code: result.css.toString(),
104+
map: merge(map, result.map),
105+
errors: []
106+
}
107+
}
108+
109+
return { code: result.css.toString(), errors: [] }
110+
}
111+
}
112+
113+
// .styl
114+
const styl = {
115+
render(
116+
source: string,
117+
map: any | null,
118+
options: any
119+
): StylePreprocessorResults {
120+
const nodeStylus = customRequire('stylus', options)
121+
try {
122+
const ref = nodeStylus(source)
123+
Object.keys(options).forEach(key => ref.set(key, options[key]))
124+
if (map) ref.set('sourcemap', { inline: false, comment: false })
125+
126+
const result = ref.render()
127+
if (map) {
128+
return {
129+
code: result,
130+
map: merge(map, ref.sourcemap),
131+
errors: []
132+
}
133+
}
134+
135+
return { code: result, errors: [] }
136+
} catch (e) {
137+
return { code: '', errors: [e] }
138+
}
139+
}
140+
}
141+
142+
export const processors: { [key: string]: StylePreprocessor } = {
143+
less,
144+
sass,
145+
scss,
146+
styl,
147+
stylus: styl
148+
}

build/webpack.config.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -158,20 +158,12 @@ ${ pkg.name } v${ pkg.version }
158158
@license ${ pkg.license }
159159
`.trim()),
160160
],
161-
externals: {
162-
'vue': {
163-
amd: 'vue',
164-
commonjs: 'vue',
165-
commonjs2: 'vue',
166-
root: 'Vue'
167-
},
168-
},
169161
resolve: {
170162
extensions: [".ts", ".js"],
171163
mainFields: ['browser', 'main', 'module'],
172164
alias: {
173165

174-
'./createSFCModule.ts': `./createVue${ vueVersion }SFCModule.ts`,
166+
'./createSFCModule': `./createVue${ vueVersion }SFCModule`,
175167

176168
// dedupe (see DuplicatePackageCheckerPlugin result)
177169
'bn.js': require.resolve('bn.js'),
@@ -216,9 +208,11 @@ ${ pkg.name } v${ pkg.version }
216208
'less': false,
217209
'prettier': false,
218210
'./buble.js': Path.resolve(__dirname, 'fakeBuble.mjs'), // used by vue-template-es2015-compiler
211+
'./styleProcessors': Path.resolve(__dirname, 'vue2StyleProcessors.ts'), // used by @vue/component-compiler-utils
219212

220213
...!genSourcemap ? {
221214
'source-map': false,
215+
'merge-source-map': false,
222216
} : {},
223217

224218
...isProd ? {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"vm-browserify": "^1.1.2"
6767
},
6868
"devDependencies": {
69+
"@types/spark-md5": "^3.0.2",
6970
"@vue/component-compiler-utils": "^3.2.0",
7071
"babel-loader": "^8.2.2",
7172
"caniuse-api": "^3.0.0",

src/createSFCModule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import { LoadModule, ModuleExport, Options } from './types'
22

3-
declare export async function createSFCModule(source : string, filename : string, options : Options, loadModule : LoadModule) : Promise<ModuleExport>
3+
export declare function createSFCModule (source: string, filename: string, options: Options, loadModule: LoadModule): Promise<ModuleExport>

src/createVue2SFCModule.ts

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import {
33
compileStyleAsync as sfc_compileStyleAsync,
44
compileTemplate as sfc_compileTemplate,
5-
parse as sfc_parse,
5+
parse as sfc_parse, StyleCompileOptions,
66
} from '@vue/component-compiler-utils'
77

88
import * as vueTemplateCompiler from 'vue-template-compiler'
@@ -24,9 +24,26 @@ import {
2424
import babelPluginTransformModulesCommonjs from '@babel/plugin-transform-modules-commonjs'
2525

2626

27-
import { formatError, withCache, hash, renameDynamicImport, parseDeps, interopRequireDefault, transformJSCode, loadDeps, createModule } from './tools.ts'
27+
import {
28+
formatError,
29+
formatErrorStartEnd,
30+
withCache,
31+
hash,
32+
renameDynamicImport,
33+
parseDeps,
34+
interopRequireDefault,
35+
transformJSCode,
36+
loadDeps,
37+
createModule,
38+
formatErrorLineColumn
39+
} from './tools'
2840

29-
import { Options, LoadModule, ModuleExport, CustomBlockCallback } from './types.ts'
41+
import {
42+
Options,
43+
LoadModule,
44+
ModuleExport,
45+
CustomBlockCallback
46+
} from './types'
3047

3148
/**
3249
* the version of the library (process.env.VERSION is set by webpack, at compile-time)
@@ -51,7 +68,7 @@ export async function createSFCModule(source : string, filename : string, option
5168
const component = {};
5269

5370

54-
const { compiledCache, addStyle, log, additionalBabelPlugins = [], customBlockHandler } = options;
71+
const { moduleCache, compiledCache, addStyle, log, additionalBabelPlugins = [], customBlockHandler } = options;
5572

5673
// vue-loader next: https://github.com/vuejs/vue-loader/blob/next/src/index.ts#L91
5774
const descriptor = sfc_parse({
@@ -73,17 +90,31 @@ export async function createSFCModule(source : string, filename : string, option
7390

7491
const hasScoped = descriptor.styles.some(e => e.scoped);
7592

93+
// https://github.com/vuejs/vue-loader/blob/b53ae44e4b9958db290f5918248071e9d2445d38/lib/runtime/componentNormalizer.js#L36
94+
if (hasScoped) {
95+
Object.assign(component, {_scopeId: scopeId});
96+
}
97+
7698
const compileTemplateOptions : TemplateCompileOptions = descriptor.template ? {
7799
// hack, since sourceMap is not configurable an we want to get rid of source-map dependency. see genSourcemap
78100
source: descriptor.template.content,
79101
filename,
80102
compiler: vueTemplateCompiler as VueTemplateCompiler,
81-
compilerOptions: undefined,
82-
preprocessLang: descriptor.template.lang,
103+
compilerOptions: {
104+
outputSourceRange: true,
105+
scopeId: hasScoped ? scopeId : null,
106+
comments: true
107+
} as any,
83108
isProduction: isProd,
84109
prettify: false
85110
} : null;
86111

112+
// Vue2 doesn't support preprocessCustomRequire, so we have to preprocess manually
113+
if (descriptor.template?.lang) {
114+
const preprocess = moduleCache[descriptor.template.lang] as any
115+
compileTemplateOptions.source = preprocess.render(compileTemplateOptions.source, compileTemplateOptions.preprocessOptions)
116+
}
117+
87118
if ( descriptor.script ) {
88119

89120
// eg: https://github.com/vuejs/vue-loader/blob/v15.9.6/lib/index.js
@@ -105,7 +136,7 @@ export async function createSFCModule(source : string, filename : string, option
105136
});
106137

107138
} catch(ex) {
108-
log?.('error', 'SFC script', formatError(ex.message, filename, source, ex.loc.line, ex.loc.column + 1) );
139+
log?.('error', 'SFC script', formatErrorLineColumn(ex.message, filename, source, ex.loc.line, ex.loc.column + 1) );
109140
throw ex;
110141
}
111142

@@ -139,20 +170,36 @@ export async function createSFCModule(source : string, filename : string, option
139170

140171
const template = sfc_compileTemplate(compileTemplateOptions);
141172
// "@vue/component-compiler-utils" does NOT assume any module system, and expose render in global scope.
142-
template.code += "\nmodule.exports = {render: render, staticRenderFns: staticRenderFns}"
173+
template.code += `\nmodule.exports = { render, staticRenderFns }`
143174

144175
if ( template.errors.length ) {
145176

146177
preventCache();
147-
for ( const err of template.errors ) {
178+
for ( let err of template.errors ) {
179+
if (typeof err !== 'object') {
180+
err = {
181+
msg: err,
182+
start: undefined,
183+
end: undefined
184+
}
185+
}
148186

149187
// @ts-ignore (Property 'message' does not exist on type 'string | CompilerError')
150-
log?.('error', 'SFC template', err );
188+
log?.('error', 'SFC template', formatErrorStartEnd(err.msg, filename, compileTemplateOptions.source.trim(), err.start, err.end ));
151189
}
152190
}
153191

154-
for ( const err of template.tips )
155-
log?.('info', 'SFC template', err);
192+
for ( let err of template.tips ) {
193+
if (typeof err !== 'object') {
194+
err = {
195+
msg: err,
196+
start: undefined,
197+
end: undefined
198+
}
199+
}
200+
201+
log?.('info', 'SFC template', formatErrorStartEnd(err.msg, filename, source, err.start, err.end ));
202+
}
156203

157204
return await transformJSCode(template.code, true, filename, options);
158205
});
@@ -169,24 +216,30 @@ export async function createSFCModule(source : string, filename : string, option
169216
await loadModule(descStyle.lang, options);
170217

171218
const style = await withCache(compiledCache, [ componentHash, descStyle.content ], async ({ preventCache }) => {
172-
173219
// src: https://github.com/vuejs/vue-next/blob/15baaf14f025f6b1d46174c9713a2ec517741d0d/packages/compiler-sfc/src/compileStyle.ts#L70
174-
const compiledStyle = await sfc_compileStyleAsync({
220+
221+
const compileStyleOptions: StyleCompileOptions = {
175222
source: descStyle.content,
176223
filename,
177224
id: scopeId,
178225
scoped: descStyle.scoped,
179-
trim: false, // Should we enable it, as it requires postcss trimPlugin ?
180-
preprocessLang: descStyle.lang
181-
});
226+
trim: false,
227+
preprocessLang: descStyle.lang,
228+
preprocessOptions: {
229+
preprocessOptions: {
230+
customRequire: (id: string) => moduleCache[id]
231+
}
232+
}
233+
}
182234

235+
const compiledStyle = await sfc_compileStyleAsync(compileStyleOptions);
183236
if ( compiledStyle.errors.length ) {
184237

185238
preventCache();
186239
for ( const err of compiledStyle.errors ) {
187240

188241
// @ts-ignore (Property 'line' does not exist on type 'Error' and Property 'column' does not exist on type 'Error')
189-
log?.('error', 'SFC style', formatError(err.message, filename, source, err.line, err.column) );
242+
log?.('error', 'SFC style', formatError(err, filename, descStyle.content));
190243
}
191244
}
192245

0 commit comments

Comments
 (0)