Skip to content

Commit fd33cad

Browse files
committed
feat: support for new script setup and css var injection
1 parent f9dd610 commit fd33cad

9 files changed

+215
-92
lines changed

example/ScriptSetup.vue

+9-9
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,30 @@
33
<div>
44
{{ count }} <button @click="inc">+</button>
55
<button @click="changeColor">change color</button>
6-
<Button/>
6+
<Button />
77
</div>
88
</template>
99

1010
<script setup>
1111
import { ref } from 'vue'
12-
export { default as Button } from './Button.vue'
12+
import Button from './Button.vue'
1313
14-
export const count = ref(0)
14+
const count = ref(0)
1515
16-
export function inc() {
16+
function inc() {
1717
count.value++
1818
}
1919
20-
export const hello = 'hi from script setup'
20+
const hello = 'hi from script'
2121
22-
export const color = ref('red')
23-
export const changeColor = () => {
22+
const color = ref('cyan')
23+
const changeColor = () => {
2424
color.value = color.value === 'red' ? 'green' : 'red'
2525
}
2626
</script>
2727

28-
<style scoped vars="{ color }">
28+
<style>
2929
h2 {
30-
color: var(--color)
30+
color: v-bind(color);
3131
}
3232
</style>

example/webpack.config.js

+29-29
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,40 @@ module.exports = (env = {}) => {
1313
return {
1414
mode: isProd ? 'production' : 'development',
1515
entry: path.resolve(__dirname, './main.js'),
16-
devtool: isProd ? 'source-map' : 'cheap-module-eval-source-map',
16+
devtool: 'source-map',
1717
output: {
1818
path: path.resolve(__dirname, 'dist'),
1919
filename: '[name].js',
20-
publicPath: '/dist/'
20+
publicPath: '/dist/',
2121
},
2222
module: {
2323
rules: [
2424
{
2525
test: /\.vue$/,
26-
loader: 'vue-loader'
26+
loader: 'vue-loader',
2727
},
2828
{
2929
test: /\.png$/,
3030
use: [
3131
{
3232
loader: 'url-loader',
3333
options: {
34-
limit: 8192
35-
}
36-
}
37-
]
34+
limit: 8192,
35+
},
36+
},
37+
],
3838
},
3939
{
4040
test: /\.css$/,
4141
use: [
4242
{
4343
loader: MiniCssExtractPlugin.loader,
4444
options: {
45-
hmr: !isProd
46-
}
45+
hmr: !isProd,
46+
},
4747
},
48-
'css-loader'
49-
]
48+
'css-loader',
49+
],
5050
},
5151
{
5252
test: /\.js$/,
@@ -58,8 +58,8 @@ module.exports = (env = {}) => {
5858
fs.readFileSync(path.resolve(__dirname, '../package.json')) +
5959
JSON.stringify(env)
6060
),
61-
cacheDirectory: path.resolve(__dirname, '../.cache')
62-
}
61+
cacheDirectory: path.resolve(__dirname, '../.cache'),
62+
},
6363
},
6464
...(babel
6565
? [
@@ -69,42 +69,42 @@ module.exports = (env = {}) => {
6969
// use yarn build-example --env.noMinimize to verify that
7070
// babel is properly applied to all js code, including the
7171
// render function compiled from SFC templates.
72-
presets: ['@babel/preset-env']
73-
}
74-
}
72+
presets: ['@babel/preset-env'],
73+
},
74+
},
7575
]
76-
: [])
77-
]
76+
: []),
77+
],
7878
},
7979
// target <docs> custom blocks
8080
{
8181
resourceQuery: /blockType=docs/,
82-
loader: require.resolve('./docs-loader')
83-
}
84-
]
82+
loader: require.resolve('./docs-loader'),
83+
},
84+
],
8585
},
8686
plugins: [
8787
new VueLoaderPlugin(),
8888
new MiniCssExtractPlugin({
89-
filename: '[name].css'
89+
filename: '[name].css',
9090
}),
9191
new webpack.DefinePlugin({
9292
__VUE_OPTIONS_API__: true,
93-
__VUE_PROD_DEVTOOLS__: false
94-
})
93+
__VUE_PROD_DEVTOOLS__: false,
94+
}),
9595
],
9696
optimization: {
97-
minimize
97+
minimize,
9898
},
9999
devServer: {
100100
stats: 'minimal',
101101
contentBase: __dirname,
102-
overlay: true
102+
overlay: true,
103103
},
104104
resolveLoader: {
105105
alias: {
106-
'vue-loader': require.resolve('../')
107-
}
108-
}
106+
'vue-loader': require.resolve('../'),
107+
},
108+
},
109109
}
110110
}

src/descriptorCache.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as fs from 'fs'
2+
import { SFCDescriptor } from '@vue/compiler-sfc'
3+
import { parse } from '@vue/compiler-sfc'
4+
5+
const cache = new Map<string, SFCDescriptor>()
6+
7+
export function setDescriptor(filename: string, entry: SFCDescriptor) {
8+
cache.set(cleanQuery(filename), entry)
9+
}
10+
11+
export function getDescriptor(filename: string): SFCDescriptor {
12+
filename = cleanQuery(filename)
13+
if (cache.has(filename)) {
14+
return cache.get(filename)!
15+
}
16+
17+
// This function should only be called after the descriptor has been
18+
// cached by the main loader.
19+
// If this is somehow called without a cache hit, it's probably due to sub
20+
// loaders being run in separate threads. The only way to deal with this is to
21+
// read from disk directly...
22+
const source = fs.readFileSync(filename, 'utf-8')
23+
const { descriptor } = parse(source, {
24+
filename,
25+
sourceMap: true,
26+
})
27+
cache.set(filename, descriptor)
28+
return descriptor
29+
}
30+
31+
function cleanQuery(str: string) {
32+
const i = str.indexOf('?')
33+
return i > 0 ? str.slice(0, i) : str
34+
}

src/index.ts

+41-39
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,21 @@ import hash = require('hash-sum')
1616

1717
import {
1818
parse,
19-
compileScript,
2019
TemplateCompiler,
2120
CompilerOptions,
2221
SFCBlock,
2322
SFCTemplateCompileOptions,
2423
SFCScriptCompileOptions,
2524
SFCStyleBlock,
26-
SFCScriptBlock,
2725
} from '@vue/compiler-sfc'
2826
import { selectBlock } from './select'
2927
import { genHotReloadCode } from './hotReload'
3028
import { genCSSModulesCode } from './cssModules'
3129
import { formatError } from './formatError'
3230

3331
import VueLoaderPlugin from './plugin'
32+
import { resolveScript } from './resolveScript'
33+
import { setDescriptor } from './descriptorCache'
3434

3535
export { VueLoaderPlugin }
3636

@@ -93,6 +93,9 @@ export default function loader(
9393
sourceMap,
9494
})
9595

96+
// cache descriptor
97+
setDescriptor(resourcePath, descriptor)
98+
9699
if (errors.length) {
97100
errors.forEach((err) => {
98101
formatError(err, source, resourcePath)
@@ -101,73 +104,72 @@ export default function loader(
101104
return ``
102105
}
103106

107+
// module id for scoped CSS & hot-reload
108+
const rawShortFilePath = path
109+
.relative(rootContext || process.cwd(), resourcePath)
110+
.replace(/^(\.\.[\/\\])+/, '')
111+
const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery
112+
const id = hash(
113+
isProduction
114+
? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
115+
: shortFilePath
116+
)
117+
104118
// if the query has a type field, this is a language block request
105119
// e.g. foo.vue?type=template&id=xxxxx
106120
// and we will return early
107121
if (incomingQuery.type) {
108122
return selectBlock(
109123
descriptor,
124+
id,
125+
options,
110126
loaderContext,
111127
incomingQuery,
112128
!!options.appendExtension
113129
)
114130
}
115131

116-
// module id for scoped CSS & hot-reload
117-
const rawShortFilePath = path
118-
.relative(rootContext || process.cwd(), resourcePath)
119-
.replace(/^(\.\.[\/\\])+/, '')
120-
const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery
121-
const id = hash(
122-
isProduction
123-
? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
124-
: shortFilePath
125-
)
126-
127132
// feature information
128133
const hasScoped = descriptor.styles.some((s) => s.scoped)
129134
const needsHotReload =
130135
!isServer &&
131136
!isProduction &&
132-
!!(descriptor.script || descriptor.template) &&
137+
!!(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
133138
options.hotReload !== false
134139

135140
// script
136-
let script: SFCScriptBlock | undefined
137141
let scriptImport = `const script = {}`
138-
if (descriptor.script || descriptor.scriptSetup) {
139-
try {
140-
script = (descriptor as any).scriptCompiled = compileScript(descriptor, {
141-
babelParserPlugins: options.babelParserPlugins,
142-
})
143-
} catch (e) {
144-
loaderContext.emitError(e)
145-
}
146-
if (script) {
147-
const src = script.src || resourcePath
148-
const attrsQuery = attrsToQuery(script.attrs, 'js')
149-
const query = `?vue&type=script${attrsQuery}${resourceQuery}`
150-
const scriptRequest = stringifyRequest(src + query)
151-
scriptImport =
152-
`import script from ${scriptRequest}\n` +
153-
// support named exports
154-
`export * from ${scriptRequest}`
155-
}
142+
const script = resolveScript(descriptor, id, options, loaderContext)
143+
if (script) {
144+
const src = script.src || resourcePath
145+
const attrsQuery = attrsToQuery(script.attrs, 'js')
146+
const query = `?vue&type=script${attrsQuery}${resourceQuery}`
147+
const scriptRequest = stringifyRequest(src + query)
148+
scriptImport =
149+
`import script from ${scriptRequest}\n` +
150+
// support named exports
151+
`export * from ${scriptRequest}`
156152
}
157153

158154
// template
159155
let templateImport = ``
160156
let templateRequest
161157
const renderFnName = isServer ? `ssrRender` : `render`
162-
if (descriptor.template) {
158+
const templateLang = descriptor.template && descriptor.template.lang
159+
const useInlineTemplate =
160+
descriptor.scriptSetup && isProduction && !isServer && !templateLang
161+
if (descriptor.template && !useInlineTemplate) {
163162
const src = descriptor.template.src || resourcePath
164163
const idQuery = `&id=${id}`
165164
const scopedQuery = hasScoped ? `&scoped=true` : ``
166165
const attrsQuery = attrsToQuery(descriptor.template.attrs)
167-
const bindingsQuery = script
168-
? `&bindings=${JSON.stringify(script.bindings ?? {})}`
169-
: ``
170-
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${bindingsQuery}${resourceQuery}`
166+
// const bindingsQuery = script
167+
// ? `&bindings=${JSON.stringify(script.bindings ?? {})}`
168+
// : ``
169+
// const varsQuery = descriptor.cssVars
170+
// ? `&vars=${qs.escape(generateCssVars(descriptor, id, isProduction))}`
171+
// : ``
172+
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${resourceQuery}`
171173
templateRequest = stringifyRequest(src + query)
172174
templateImport = `import { ${renderFnName} } from ${templateRequest}`
173175
}
@@ -184,7 +186,7 @@ export default function loader(
184186
const attrsQuery = attrsToQuery(style.attrs, 'css')
185187
// make sure to only pass id when necessary so that we don't inject
186188
// duplicate tags when multiple components import the same css file
187-
const idQuery = style.scoped ? `&id=${id}` : ``
189+
const idQuery = !style.src || style.scoped ? `&id=${id}` : ``
188190
const query = `?vue&type=style&index=${i}${idQuery}${attrsQuery}${resourceQuery}`
189191
const styleRequest = stringifyRequest(src + query)
190192
if (style.module) {

0 commit comments

Comments
 (0)