Skip to content

Commit

Permalink
feat: support for new script setup and css var injection
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Nov 20, 2020
1 parent f9dd610 commit fd33cad
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 92 deletions.
18 changes: 9 additions & 9 deletions example/ScriptSetup.vue
Expand Up @@ -3,30 +3,30 @@
<div>
{{ count }} <button @click="inc">+</button>
<button @click="changeColor">change color</button>
<Button/>
<Button />
</div>
</template>

<script setup>
import { ref } from 'vue'
export { default as Button } from './Button.vue'
import Button from './Button.vue'
export const count = ref(0)
const count = ref(0)
export function inc() {
function inc() {
count.value++
}
export const hello = 'hi from script setup'
const hello = 'hi from script'
export const color = ref('red')
export const changeColor = () => {
const color = ref('cyan')
const changeColor = () => {
color.value = color.value === 'red' ? 'green' : 'red'
}
</script>

<style scoped vars="{ color }">
<style>
h2 {
color: var(--color)
color: v-bind(color);
}
</style>
58 changes: 29 additions & 29 deletions example/webpack.config.js
Expand Up @@ -13,40 +13,40 @@ module.exports = (env = {}) => {
return {
mode: isProd ? 'production' : 'development',
entry: path.resolve(__dirname, './main.js'),
devtool: isProd ? 'source-map' : 'cheap-module-eval-source-map',
devtool: 'source-map',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
publicPath: '/dist/'
publicPath: '/dist/',
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
loader: 'vue-loader',
},
{
test: /\.png$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192
}
}
]
limit: 8192,
},
},
],
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: !isProd
}
hmr: !isProd,
},
},
'css-loader'
]
'css-loader',
],
},
{
test: /\.js$/,
Expand All @@ -58,8 +58,8 @@ module.exports = (env = {}) => {
fs.readFileSync(path.resolve(__dirname, '../package.json')) +
JSON.stringify(env)
),
cacheDirectory: path.resolve(__dirname, '../.cache')
}
cacheDirectory: path.resolve(__dirname, '../.cache'),
},
},
...(babel
? [
Expand All @@ -69,42 +69,42 @@ module.exports = (env = {}) => {
// use yarn build-example --env.noMinimize to verify that
// babel is properly applied to all js code, including the
// render function compiled from SFC templates.
presets: ['@babel/preset-env']
}
}
presets: ['@babel/preset-env'],
},
},
]
: [])
]
: []),
],
},
// target <docs> custom blocks
{
resourceQuery: /blockType=docs/,
loader: require.resolve('./docs-loader')
}
]
loader: require.resolve('./docs-loader'),
},
],
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: '[name].css'
filename: '[name].css',
}),
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false
})
__VUE_PROD_DEVTOOLS__: false,
}),
],
optimization: {
minimize
minimize,
},
devServer: {
stats: 'minimal',
contentBase: __dirname,
overlay: true
overlay: true,
},
resolveLoader: {
alias: {
'vue-loader': require.resolve('../')
}
}
'vue-loader': require.resolve('../'),
},
},
}
}
34 changes: 34 additions & 0 deletions src/descriptorCache.ts
@@ -0,0 +1,34 @@
import * as fs from 'fs'
import { SFCDescriptor } from '@vue/compiler-sfc'
import { parse } from '@vue/compiler-sfc'

const cache = new Map<string, SFCDescriptor>()

export function setDescriptor(filename: string, entry: SFCDescriptor) {
cache.set(cleanQuery(filename), entry)
}

export function getDescriptor(filename: string): SFCDescriptor {
filename = cleanQuery(filename)
if (cache.has(filename)) {
return cache.get(filename)!
}

// This function should only be called after the descriptor has been
// cached by the main loader.
// If this is somehow called without a cache hit, it's probably due to sub
// loaders being run in separate threads. The only way to deal with this is to
// read from disk directly...
const source = fs.readFileSync(filename, 'utf-8')
const { descriptor } = parse(source, {
filename,
sourceMap: true,
})
cache.set(filename, descriptor)
return descriptor
}

function cleanQuery(str: string) {
const i = str.indexOf('?')
return i > 0 ? str.slice(0, i) : str
}
80 changes: 41 additions & 39 deletions src/index.ts
Expand Up @@ -16,21 +16,21 @@ import hash = require('hash-sum')

import {
parse,
compileScript,
TemplateCompiler,
CompilerOptions,
SFCBlock,
SFCTemplateCompileOptions,
SFCScriptCompileOptions,
SFCStyleBlock,
SFCScriptBlock,
} from '@vue/compiler-sfc'
import { selectBlock } from './select'
import { genHotReloadCode } from './hotReload'
import { genCSSModulesCode } from './cssModules'
import { formatError } from './formatError'

import VueLoaderPlugin from './plugin'
import { resolveScript } from './resolveScript'
import { setDescriptor } from './descriptorCache'

export { VueLoaderPlugin }

Expand Down Expand Up @@ -93,6 +93,9 @@ export default function loader(
sourceMap,
})

// cache descriptor
setDescriptor(resourcePath, descriptor)

if (errors.length) {
errors.forEach((err) => {
formatError(err, source, resourcePath)
Expand All @@ -101,73 +104,72 @@ export default function loader(
return ``
}

// module id for scoped CSS & hot-reload
const rawShortFilePath = path
.relative(rootContext || process.cwd(), resourcePath)
.replace(/^(\.\.[\/\\])+/, '')
const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery
const id = hash(
isProduction
? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
: shortFilePath
)

// if the query has a type field, this is a language block request
// e.g. foo.vue?type=template&id=xxxxx
// and we will return early
if (incomingQuery.type) {
return selectBlock(
descriptor,
id,
options,
loaderContext,
incomingQuery,
!!options.appendExtension
)
}

// module id for scoped CSS & hot-reload
const rawShortFilePath = path
.relative(rootContext || process.cwd(), resourcePath)
.replace(/^(\.\.[\/\\])+/, '')
const shortFilePath = rawShortFilePath.replace(/\\/g, '/') + resourceQuery
const id = hash(
isProduction
? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
: shortFilePath
)

// feature information
const hasScoped = descriptor.styles.some((s) => s.scoped)
const needsHotReload =
!isServer &&
!isProduction &&
!!(descriptor.script || descriptor.template) &&
!!(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
options.hotReload !== false

// script
let script: SFCScriptBlock | undefined
let scriptImport = `const script = {}`
if (descriptor.script || descriptor.scriptSetup) {
try {
script = (descriptor as any).scriptCompiled = compileScript(descriptor, {
babelParserPlugins: options.babelParserPlugins,
})
} catch (e) {
loaderContext.emitError(e)
}
if (script) {
const src = script.src || resourcePath
const attrsQuery = attrsToQuery(script.attrs, 'js')
const query = `?vue&type=script${attrsQuery}${resourceQuery}`
const scriptRequest = stringifyRequest(src + query)
scriptImport =
`import script from ${scriptRequest}\n` +
// support named exports
`export * from ${scriptRequest}`
}
const script = resolveScript(descriptor, id, options, loaderContext)
if (script) {
const src = script.src || resourcePath
const attrsQuery = attrsToQuery(script.attrs, 'js')
const query = `?vue&type=script${attrsQuery}${resourceQuery}`
const scriptRequest = stringifyRequest(src + query)
scriptImport =
`import script from ${scriptRequest}\n` +
// support named exports
`export * from ${scriptRequest}`
}

// template
let templateImport = ``
let templateRequest
const renderFnName = isServer ? `ssrRender` : `render`
if (descriptor.template) {
const templateLang = descriptor.template && descriptor.template.lang
const useInlineTemplate =
descriptor.scriptSetup && isProduction && !isServer && !templateLang
if (descriptor.template && !useInlineTemplate) {
const src = descriptor.template.src || resourcePath
const idQuery = `&id=${id}`
const scopedQuery = hasScoped ? `&scoped=true` : ``
const attrsQuery = attrsToQuery(descriptor.template.attrs)
const bindingsQuery = script
? `&bindings=${JSON.stringify(script.bindings ?? {})}`
: ``
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${bindingsQuery}${resourceQuery}`
// const bindingsQuery = script
// ? `&bindings=${JSON.stringify(script.bindings ?? {})}`
// : ``
// const varsQuery = descriptor.cssVars
// ? `&vars=${qs.escape(generateCssVars(descriptor, id, isProduction))}`
// : ``
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${resourceQuery}`
templateRequest = stringifyRequest(src + query)
templateImport = `import { ${renderFnName} } from ${templateRequest}`
}
Expand All @@ -184,7 +186,7 @@ export default function loader(
const attrsQuery = attrsToQuery(style.attrs, 'css')
// make sure to only pass id when necessary so that we don't inject
// duplicate tags when multiple components import the same css file
const idQuery = style.scoped ? `&id=${id}` : ``
const idQuery = !style.src || style.scoped ? `&id=${id}` : ``
const query = `?vue&type=style&index=${i}${idQuery}${attrsQuery}${resourceQuery}`
const styleRequest = stringifyRequest(src + query)
if (style.module) {
Expand Down

0 comments on commit fd33cad

Please sign in to comment.