Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

Commit 5636947

Browse files
authored
feat: process custom blocks (#354)
1 parent d790196 commit 5636947

File tree

4 files changed

+173
-11
lines changed

4 files changed

+173
-11
lines changed

Diff for: .github/workflows/ci.yaml

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: CI
22

3-
on: [push, pull_request]
3+
on: [push]
44

55
defaults:
66
run:
@@ -35,3 +35,8 @@ jobs:
3535
3636
- name: Run unit tests
3737
run: yarn test:unit --coverage
38+
39+
- uses: actions/upload-artifact@v2
40+
with:
41+
name: coverage
42+
path: coverage/

Diff for: jest.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ module.exports = {
66
transform: {
77
'^.+\\.ts$': 'ts-jest',
88
},
9-
collectCoverageFrom: ['<rootDir>/src/.*\\.ts'],
109
coveragePathIgnorePatterns: ['.*\\.spec\\.ts'],
1110
globals: {
1211
'ts-jest': {

Diff for: src/index.spec.ts

+98-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ describe('Rollup Plugin Vue', () => {
44
describe('transform', () => {
55
let transform: (code: string, fileName: string) => Promise<{ code: string }>
66
beforeEach(() => {
7-
transform = PluginVue().transform as any
7+
transform = PluginVue({ customBlocks: ['*'] }).transform as any
88
})
99

1010
it('should transform <script> block', async () => {
@@ -53,10 +53,9 @@ describe('Rollup Plugin Vue', () => {
5353

5454
expect(code).toEqual(
5555
expect.stringContaining(
56-
`import { render } from "example.vue?vue&type=template&id=`
56+
`import { render } from "example.vue?vue&type=template&id=063a7d4c"`
5757
)
5858
)
59-
expect(code).not.toEqual(expect.stringContaining(`lang.html`))
6059
expect(code).toEqual(expect.stringContaining(`script.render = render`))
6160
})
6261

@@ -67,11 +66,105 @@ describe('Rollup Plugin Vue', () => {
6766
)
6867
expect(code).toEqual(
6968
expect.stringContaining(
70-
`import { render } from "example.vue?vue&type=template&id=`
69+
`import { render } from "example.vue?vue&type=template&id=063a7d4c"`
7170
)
7271
)
73-
expect(code).not.toEqual(expect.stringContaining(`lang.pug`))
7472
expect(code).toEqual(expect.stringContaining(`script.render = render`))
7573
})
74+
75+
it('should transform <style> block', async () => {
76+
const { code } = await transform(`<style>.foo {}</style>`, `example.vue`)
77+
expect(code).toEqual(
78+
expect.stringContaining(
79+
`import "example.vue?vue&type=style&index=0&lang.css"`
80+
)
81+
)
82+
})
83+
84+
it('should transform <style scoped> block', async () => {
85+
const { code } = await transform(
86+
`<style scoped>.foo {}</style>`,
87+
`example.vue`
88+
)
89+
expect(code).toEqual(
90+
expect.stringContaining(
91+
`import "example.vue?vue&type=style&index=0&id=063a7d4c&scoped=true&lang.css`
92+
)
93+
)
94+
})
95+
96+
it('should transform <style module> block', async () => {
97+
const { code } = await transform(
98+
`<style module>.foo {}</style>`,
99+
`example.vue`
100+
)
101+
expect(code).toEqual(
102+
expect.stringContaining(
103+
`import "example.vue?vue&type=style&index=0&lang.css`
104+
)
105+
)
106+
expect(code).toEqual(
107+
expect.stringContaining(
108+
`import style0 from "example.vue?vue&type=style&index=0&module=true&lang.css`
109+
)
110+
)
111+
expect(code).toEqual(expect.stringContaining('script.__cssModules = {}'))
112+
expect(code).toEqual(
113+
expect.stringContaining('cssModules["$style"] = style0')
114+
)
115+
})
116+
117+
it('should transform <style module="custom"> block', async () => {
118+
const { code } = await transform(
119+
`<style module="custom">.foo {}</style>`,
120+
`example.vue`
121+
)
122+
expect(code).toEqual(
123+
expect.stringContaining(
124+
`import "example.vue?vue&type=style&index=0&lang.css`
125+
)
126+
)
127+
expect(code).toEqual(
128+
expect.stringContaining(
129+
`import style0 from "example.vue?vue&type=style&index=0&module=custom&lang.css`
130+
)
131+
)
132+
expect(code).toEqual(expect.stringContaining('script.__cssModules = {}'))
133+
expect(code).toEqual(
134+
expect.stringContaining('cssModules["custom"] = style0')
135+
)
136+
})
137+
138+
it.skip('should transform multiple <style module> block', async () => {
139+
await transform(
140+
`<style module>.foo {}</style>
141+
<style module>.bar {}</style>`,
142+
`example.vue`
143+
)
144+
// TODO: Maybe warn about duplicate css module?
145+
})
146+
147+
it('should transform <i18n> block', async () => {
148+
const { code } = await transform(`<i18n>{}</i18n>`, `example.vue`)
149+
expect(code).toEqual(
150+
expect.stringContaining(
151+
`import block0 from "example.vue?vue&type=i18n&index=0&lang.i18n`
152+
)
153+
)
154+
expect(code).toEqual(expect.stringContaining('block0(script)'))
155+
})
156+
157+
it('should transform <i18n lang="json"> block', async () => {
158+
const { code } = await transform(
159+
`<i18n lang="json">{}</i18n>`,
160+
`example.vue`
161+
)
162+
expect(code).toEqual(
163+
expect.stringContaining(
164+
`import block0 from "example.vue?vue&type=i18n&index=0&lang.json`
165+
)
166+
)
167+
expect(code).toEqual(expect.stringContaining('block0(script)'))
168+
})
76169
})
77170
})

Diff for: src/index.ts

+69-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export interface Options
3939
target: 'node' | 'browser'
4040
exposeFilename: boolean
4141

42+
customBlocks?: string[]
43+
4244
// if true, handle preprocessors directly instead of delegating to other
4345
// rollup plugins
4446
preprocessStyles?: boolean
@@ -61,6 +63,7 @@ const defaultOptions: Options = {
6163
exclude: [],
6264
target: 'browser',
6365
exposeFilename: false,
66+
customBlocks: [],
6467
}
6568

6669
export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
@@ -75,6 +78,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
7578
const rootContext = process.cwd()
7679

7780
const filter = createFilter(options.include, options.exclude)
81+
const filterCustomBlock = createCustomBlockFilter(options.customBlocks)
7882

7983
return {
8084
name: 'vue',
@@ -88,6 +92,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
8892
const [filename] = id.split('?', 2)
8993
cache.set(filename, getDescriptor(importer!))
9094
}
95+
if (!filter(query.filename)) return undefined
9196
debug(`resolveId(${id})`)
9297
return id
9398
}
@@ -128,6 +133,8 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
128133
async transform(code, id) {
129134
const query = parseVuePartRequest(id)
130135
if (query.vue) {
136+
if (!filter(query.filename)) return null
137+
131138
const descriptor = getDescriptor(query.filename)
132139
const hasScoped = descriptor.styles.some((s) => s.scoped)
133140
if (query.type === 'template') {
@@ -225,7 +232,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
225232
code,
226233
id,
227234
descriptor,
228-
{ rootContext, isProduction, isServer },
235+
{ rootContext, isProduction, isServer, filterCustomBlock },
229236
options
230237
)
231238
debug('transient .vue file:', '\n' + output + '\n')
@@ -243,6 +250,27 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
243250
}
244251
}
245252

253+
function createCustomBlockFilter(
254+
queries?: string[]
255+
): (type: string) => boolean {
256+
if (!queries || queries.length === 0) return () => false
257+
258+
const allowed = new Set(queries.filter((query) => /^[a-z]/i.test(query)))
259+
const disallowed = new Set(
260+
queries
261+
.filter((query) => /^![a-z]/i.test(query))
262+
.map((query) => query.substr(1))
263+
)
264+
const allowAll = queries.includes('*') || !queries.includes('!*')
265+
266+
return (type: string) => {
267+
if (allowed.has(type)) return true
268+
if (disallowed.has(type)) return true
269+
270+
return allowAll
271+
}
272+
}
273+
246274
type Query =
247275
| {
248276
filename: string
@@ -280,7 +308,7 @@ type Query =
280308
}
281309

282310
function parseVuePartRequest(id: string): Query {
283-
const [filename, query] = id.replace(/#\.[\w-]+$/, '').split('?', 2)
311+
const [filename, query] = id.split('?', 2)
284312

285313
if (!query) return { vue: false, filename }
286314

@@ -335,7 +363,13 @@ function transformVueSFC(
335363
rootContext,
336364
isProduction,
337365
isServer,
338-
}: { rootContext: string; isProduction: boolean; isServer: boolean },
366+
filterCustomBlock,
367+
}: {
368+
rootContext: string
369+
isProduction: boolean
370+
isServer: boolean
371+
filterCustomBlock: (type: string) => boolean
372+
},
339373
options: Options
340374
) {
341375
const shortFilePath = relative(rootContext, resourcePath)
@@ -358,10 +392,16 @@ function transformVueSFC(
358392
id,
359393
options.preprocessStyles
360394
)
395+
const customBlocksCode = getCustomBlock(
396+
descriptor,
397+
resourcePath,
398+
filterCustomBlock
399+
)
361400
const output = [
362401
scriptImport,
363402
templateImport,
364403
stylesCode,
404+
customBlocksCode,
365405
isServer ? `script.ssrRender = ssrRender` : `script.render = render`,
366406
]
367407
if (hasScoped) {
@@ -429,7 +469,10 @@ function getStyleCode(
429469
// do not include module in default query, since we use it to indicate
430470
// that the module needs to export the modules json
431471
const attrsQuery = attrsToQuery(style.attrs, 'css', preprocessStyles)
432-
const attrsQueryWithoutModule = attrsQuery.replace(/&module(=true)?/, '')
472+
const attrsQueryWithoutModule = attrsQuery.replace(
473+
/&module(=true|=[^&]+)?/,
474+
''
475+
)
433476
// make sure to only pass id when necessary so that we don't inject
434477
// duplicate tags when multiple components import the same css file
435478
const idQuery = style.scoped ? `&id=${id}` : ``
@@ -458,6 +501,28 @@ function getStyleCode(
458501
return stylesCode
459502
}
460503

504+
function getCustomBlock(
505+
descriptor: SFCDescriptor,
506+
resourcePath: string,
507+
filter: (type: string) => boolean
508+
) {
509+
let code = ''
510+
511+
descriptor.customBlocks.forEach((block, index) => {
512+
if (filter(block.type)) {
513+
const src = block.src || resourcePath
514+
const attrsQuery = attrsToQuery(block.attrs, block.type)
515+
const srcQuery = block.src ? `&src` : ``
516+
const query = `?vue&type=${block.type}&index=${index}${srcQuery}${attrsQuery}`
517+
const request = _(src + query)
518+
code += `import block${index} from ${request}\n`
519+
code += `if (typeof block${index} === 'function') block${index}(script)\n`
520+
}
521+
})
522+
523+
return code
524+
}
525+
461526
function createRollupError(id: string, error: CompilerError): RollupError {
462527
return {
463528
id,

0 commit comments

Comments
 (0)