Skip to content

Commit 1ec547b

Browse files
committed
wip: progress
1 parent 893cbf6 commit 1ec547b

File tree

6 files changed

+404
-61
lines changed

6 files changed

+404
-61
lines changed

src/helper.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/index.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { transformMain } from './main'
1616
import { handleHotUpdate } from './handleHotUpdate'
1717
import { transformTemplateAsModule } from './template'
1818
import { transformStyle } from './style'
19-
import { EXPORT_HELPER_ID, helperCode } from './helper'
19+
import { NORMALIZER_ID, normalizerCode } from './utils/componentNormalizer'
20+
import { HMR_RUNTIME_ID, hmrRuntimeCode } from './utils/hmrRuntime'
2021

2122
export { parseVueRequest } from './utils/query'
2223
export type { VueQuery } from './utils/query'
@@ -87,8 +88,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
8788
isProduction: config.isProduction,
8889
sourceMap: config.command === 'build' ? !!config.build.sourcemap : true,
8990
cssDevSourcemap: config.css?.devSourcemap ?? false,
90-
devToolsEnabled:
91-
!!config.define!.__VUE_PROD_DEVTOOLS__ || !config.isProduction
91+
devToolsEnabled: !config.isProduction
9292
}
9393
},
9494

@@ -102,7 +102,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
102102

103103
async resolveId(id) {
104104
// component export helper
105-
if (id === EXPORT_HELPER_ID) {
105+
if (id === NORMALIZER_ID || id === HMR_RUNTIME_ID) {
106106
return id
107107
}
108108
// serve sub-part requests (*?vue) as virtual modules
@@ -113,8 +113,11 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin {
113113

114114
load(id, opt) {
115115
const ssr = opt?.ssr === true
116-
if (id === EXPORT_HELPER_ID) {
117-
return helperCode
116+
if (id === NORMALIZER_ID) {
117+
return normalizerCode
118+
}
119+
if (id === HMR_RUNTIME_ID) {
120+
return hmrRuntimeCode
118121
}
119122

120123
const { filename, query } = parseVueRequest(id)

src/main.ts

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import { resolveScript } from './script'
1212
import { transformTemplateInMain } from './template'
1313
import { isOnlyTemplateChanged } from './handleHotUpdate'
1414
import { createRollupError } from './utils/error'
15-
import { EXPORT_HELPER_ID } from './helper'
1615
import type { ResolvedOptions } from '.'
16+
import { NORMALIZER_ID } from './utils/componentNormalizer'
17+
import { HMR_RUNTIME_ID } from './utils/hmrRuntime'
1718

1819
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
1920
export async function transformMain(
@@ -38,8 +39,10 @@ export async function transformMain(
3839
}
3940

4041
// feature information
41-
const attachedProps: [string, string][] = []
4242
const hasScoped = descriptor.styles.some((s) => s.scoped)
43+
const hasCssModules = descriptor.styles.some((s) => s.module)
44+
const hasFunctional =
45+
descriptor.template && descriptor.template.attrs.functional
4346

4447
// script
4548
const { code: scriptCode, map: scriptMap } = await genScriptCode(
@@ -57,17 +60,8 @@ export async function transformMain(
5760
ssr
5861
)
5962

60-
attachedProps.push(
61-
ssr ? ['ssrRender', '_sfc_ssrRender'] : ['render', '_sfc_render']
62-
)
63-
6463
// styles
65-
const stylesCode = await genStyleCode(
66-
descriptor,
67-
pluginContext,
68-
// asCustomElement,
69-
attachedProps
70-
)
64+
const stylesCode = await genStyleCode(descriptor, pluginContext)
7165

7266
// custom blocks
7367
const customBlocksCode = await genCustomBlockCode(descriptor, pluginContext)
@@ -78,15 +72,29 @@ export async function transformMain(
7872
stylesCode,
7973
customBlocksCode
8074
]
81-
if (hasScoped) {
82-
attachedProps.push([`__scopeId`, JSON.stringify(`data-v-${descriptor.id}`)])
83-
}
75+
76+
output.push(
77+
`/* normalize component */
78+
import __normalizer from "${NORMALIZER_ID}"
79+
var __component__ = /*#__PURE__*/__normalizer(
80+
_sfc_main,
81+
_sfc_render,
82+
_sfc_staticRenderFns,
83+
${hasFunctional ? 'true' : 'false'},
84+
${hasCssModules ? `_sfc_injectStyles` : `null`},
85+
${hasScoped ? JSON.stringify(descriptor.id) : 'null'},
86+
null,
87+
null
88+
)`
89+
)
90+
8491
if (devToolsEnabled || (devServer && !isProduction)) {
8592
// expose filename during serve for devtools to pickup
86-
attachedProps.push([
87-
`__file`,
88-
JSON.stringify(isProduction ? path.basename(filename) : filename)
89-
])
93+
output.push(
94+
`__component__.options.__file = ${JSON.stringify(
95+
isProduction ? path.basename(filename) : filename
96+
)}`
97+
)
9098
}
9199

92100
// HMR
@@ -96,21 +104,26 @@ export async function transformMain(
96104
!ssr &&
97105
!isProduction
98106
) {
99-
output.push(`_sfc_main.__hmrId = ${JSON.stringify(descriptor.id)}`)
107+
const id = JSON.stringify(descriptor.id)
100108
output.push(
101-
`typeof __VUE_HMR_RUNTIME__ !== 'undefined' && ` +
102-
`__VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)`
109+
`import __VUE_HMR_RUNTIME__ from "${HMR_RUNTIME_ID}"`,
110+
`if (!__VUE_HMR_RUNTIME__.isRecorded(${id})) {`,
111+
` __VUE_HMR_RUNTIME__.createRecord(${id}, __component__.options)`,
112+
`}`
103113
)
104114
// check if the template is the only thing that changed
105-
if (prevDescriptor && isOnlyTemplateChanged(prevDescriptor, descriptor)) {
115+
if (
116+
hasFunctional ||
117+
(prevDescriptor && isOnlyTemplateChanged(prevDescriptor, descriptor))
118+
) {
106119
output.push(`export const _rerender_only = true`)
107120
}
108121
output.push(
109122
`import.meta.hot.accept(({ default: updated, _rerender_only }) => {`,
110123
` if (_rerender_only) {`,
111-
` __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)`,
124+
` __VUE_HMR_RUNTIME__.rerender(${id}, updated)`,
112125
` } else {`,
113-
` __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)`,
126+
` __VUE_HMR_RUNTIME__.reload(${id}, updated)`,
114127
` }`,
115128
`})`
116129
)
@@ -123,16 +136,7 @@ export async function transformMain(
123136

124137
let resolvedMap: RawSourceMap | undefined = scriptMap
125138

126-
if (!attachedProps.length) {
127-
output.push(`export default _sfc_main`)
128-
} else {
129-
output.push(
130-
`import _export_sfc from '${EXPORT_HELPER_ID}'`,
131-
`export default /*#__PURE__*/_export_sfc(_sfc_main, [${attachedProps
132-
.map(([key, val]) => `['${key}',${val}]`)
133-
.join(',')}])`
134-
)
135-
}
139+
output.push(`export default __component__.exports`)
136140

137141
// handle TS transpilation
138142
let resolvedCode = output.join('\n')
@@ -256,9 +260,7 @@ async function genScriptCode(
256260

257261
async function genStyleCode(
258262
descriptor: SFCDescriptor,
259-
pluginContext: PluginContext,
260-
// asCustomElement: boolean,
261-
attachedProps: [string, string][]
263+
pluginContext: PluginContext
262264
) {
263265
let stylesCode = ``
264266
let cssModulesMap: Record<string, string> | undefined
@@ -306,8 +308,12 @@ async function genStyleCode(
306308
(code, [key, value]) => code + `"${key}":${value},\n`,
307309
'{\n'
308310
) + '}'
309-
stylesCode += `\nconst cssModules = ${mappingCode}`
310-
attachedProps.push([`__cssModules`, `cssModules`])
311+
stylesCode += `\nconst __cssModules = ${mappingCode}`
312+
stylesCode += `\nfunction _sfc_injectStyles(ctx) {
313+
for (var key in __cssModules) {
314+
this[key] = __cssModules[key]
315+
}
316+
}`
311317
}
312318
return stylesCode
313319
}

src/template.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import type { PluginContext, TransformPluginContext } from 'rollup'
55
import { getResolvedScript } from './script'
66
import { createRollupError } from './utils/error'
77
import type { ResolvedOptions } from '.'
8+
import path from 'path'
9+
import slash from 'slash'
10+
import { HMR_RUNTIME_ID } from './utils/hmrRuntime'
811

912
export async function transformTemplateAsModule(
1013
code: string,
@@ -20,8 +23,9 @@ export async function transformTemplateAsModule(
2023
!ssr &&
2124
!options.isProduction
2225
) {
23-
returnCode += `\nimport.meta.hot.accept(({ render }) => {
24-
__VUE_HMR_RUNTIME__.rerender(${JSON.stringify(descriptor.id)}, render)
26+
returnCode += `\nimport __VUE_HMR_RUNTIME__ from "${HMR_RUNTIME_ID}"`
27+
returnCode += `\nimport.meta.hot.accept((updated) => {
28+
__VUE_HMR_RUNTIME__.rerender(${JSON.stringify(descriptor.id)}, updated)
2529
})`
2630
}
2731

@@ -99,15 +103,38 @@ function resolveTemplateCompilerOptions(
99103
}
100104
}
101105

106+
const transformAssetUrls = options.template?.transformAssetUrls
107+
let assetUrlOptions
108+
if (options.devServer) {
109+
// during dev, inject vite base so that compiler-sfc can transform
110+
// relative paths directly to absolute paths without incurring an extra import
111+
// request
112+
if (filename.startsWith(options.root)) {
113+
assetUrlOptions = {
114+
base:
115+
(options.devServer.config.server?.origin ?? '') +
116+
options.devServer.config.base +
117+
slash(path.relative(options.root, path.dirname(filename)))
118+
}
119+
}
120+
} else if (transformAssetUrls !== false) {
121+
// build: force all asset urls into import requests so that they go through
122+
// the assets plugin for asset registration
123+
assetUrlOptions = {
124+
includeAbsolute: true
125+
}
126+
}
127+
102128
return {
129+
transformAssetUrls,
103130
...options.template,
104131
filename,
105132
isProduction: options.isProduction,
133+
isFunctional: !!block.attrs.functional,
106134
optimizeSSR: ssr,
107-
transformAssetUrls: true,
108135
transformAssetUrlsOptions: {
109136
...options.template?.transformAssetUrlsOptions,
110-
includeAbsolute: true
137+
...assetUrlOptions
111138
},
112139
preprocessLang: block.lang,
113140
preprocessOptions,

src/utils/componentNormalizer.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
export const NORMALIZER_ID = 'plugin-vue2:normalizer'
2+
3+
// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).
4+
// This module is a runtime utility for cleaner component module output and will
5+
// be included in the final webpack user bundle.
6+
export const normalizerCode = `
7+
export default function normalizeComponent (
8+
scriptExports,
9+
render,
10+
staticRenderFns,
11+
functionalTemplate,
12+
injectStyles,
13+
scopeId,
14+
moduleIdentifier, /* server only */
15+
shadowMode /* vue-cli only */
16+
) {
17+
// Vue.extend constructor export interop
18+
var options = typeof scriptExports === 'function'
19+
? scriptExports.options
20+
: scriptExports
21+
22+
// render functions
23+
if (render) {
24+
options.render = render
25+
options.staticRenderFns = staticRenderFns
26+
options._compiled = true
27+
}
28+
29+
// functional template
30+
if (functionalTemplate) {
31+
options.functional = true
32+
}
33+
34+
// scopedId
35+
if (scopeId) {
36+
options._scopeId = 'data-v-' + scopeId
37+
}
38+
39+
var hook
40+
if (moduleIdentifier) { // server build
41+
hook = function (context) {
42+
// 2.3 injection
43+
context =
44+
context || // cached call
45+
(this.$vnode && this.$vnode.ssrContext) || // stateful
46+
(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
47+
// 2.2 with runInNewContext: true
48+
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
49+
context = __VUE_SSR_CONTEXT__
50+
}
51+
// inject component styles
52+
if (injectStyles) {
53+
injectStyles.call(this, context)
54+
}
55+
// register component module identifier for async chunk inferrence
56+
if (context && context._registeredComponents) {
57+
context._registeredComponents.add(moduleIdentifier)
58+
}
59+
}
60+
// used by ssr in case component is cached and beforeCreate
61+
// never gets called
62+
options._ssrRegister = hook
63+
} else if (injectStyles) {
64+
hook = shadowMode
65+
? function () {
66+
injectStyles.call(
67+
this,
68+
(options.functional ? this.parent : this).$root.$options.shadowRoot
69+
)
70+
}
71+
: injectStyles
72+
}
73+
74+
if (hook) {
75+
if (options.functional) {
76+
// for template-only hot-reload because in that case the render fn doesn't
77+
// go through the normalizer
78+
options._injectStyles = hook
79+
// register for functional component in vue file
80+
var originalRender = options.render
81+
options.render = function renderWithStyleInjection (h, context) {
82+
hook.call(context)
83+
return originalRender(h, context)
84+
}
85+
} else {
86+
// inject component registration as beforeCreate hook
87+
var existing = options.beforeCreate
88+
options.beforeCreate = existing
89+
? [].concat(existing, hook)
90+
: [hook]
91+
}
92+
}
93+
94+
return {
95+
exports: scriptExports,
96+
options: options
97+
}
98+
}`

0 commit comments

Comments
 (0)