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

feat: process custom blocks #354

Merged
merged 3 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: CI

on: [push, pull_request]
on: [push]

defaults:
run:
Expand Down Expand Up @@ -35,3 +35,8 @@ jobs:

- name: Run unit tests
run: yarn test:unit --coverage

- uses: actions/upload-artifact@v2
with:
name: coverage
path: coverage/
1 change: 0 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ module.exports = {
transform: {
'^.+\\.ts$': 'ts-jest',
},
collectCoverageFrom: ['<rootDir>/src/.*\\.ts'],
coveragePathIgnorePatterns: ['.*\\.spec\\.ts'],
globals: {
'ts-jest': {
Expand Down
103 changes: 98 additions & 5 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ describe('Rollup Plugin Vue', () => {
describe('transform', () => {
let transform: (code: string, fileName: string) => Promise<{ code: string }>
beforeEach(() => {
transform = PluginVue().transform as any
transform = PluginVue({ customBlocks: ['*'] }).transform as any
})

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

expect(code).toEqual(
expect.stringContaining(
`import { render } from "example.vue?vue&type=template&id=`
`import { render } from "example.vue?vue&type=template&id=063a7d4c"`
)
)
expect(code).not.toEqual(expect.stringContaining(`lang.html`))
expect(code).toEqual(expect.stringContaining(`script.render = render`))
})

Expand All @@ -67,11 +66,105 @@ describe('Rollup Plugin Vue', () => {
)
expect(code).toEqual(
expect.stringContaining(
`import { render } from "example.vue?vue&type=template&id=`
`import { render } from "example.vue?vue&type=template&id=063a7d4c"`
)
)
expect(code).not.toEqual(expect.stringContaining(`lang.pug`))
expect(code).toEqual(expect.stringContaining(`script.render = render`))
})

it('should transform <style> block', async () => {
const { code } = await transform(`<style>.foo {}</style>`, `example.vue`)
expect(code).toEqual(
expect.stringContaining(
`import "example.vue?vue&type=style&index=0&lang.css"`
)
)
})

it('should transform <style scoped> block', async () => {
const { code } = await transform(
`<style scoped>.foo {}</style>`,
`example.vue`
)
expect(code).toEqual(
expect.stringContaining(
`import "example.vue?vue&type=style&index=0&id=063a7d4c&scoped=true&lang.css`
)
)
})

it('should transform <style module> block', async () => {
const { code } = await transform(
`<style module>.foo {}</style>`,
`example.vue`
)
expect(code).toEqual(
expect.stringContaining(
`import "example.vue?vue&type=style&index=0&lang.css`
)
)
expect(code).toEqual(
expect.stringContaining(
`import style0 from "example.vue?vue&type=style&index=0&module=true&lang.css`
)
)
expect(code).toEqual(expect.stringContaining('script.__cssModules = {}'))
expect(code).toEqual(
expect.stringContaining('cssModules["$style"] = style0')
)
})

it('should transform <style module="custom"> block', async () => {
const { code } = await transform(
`<style module="custom">.foo {}</style>`,
`example.vue`
)
expect(code).toEqual(
expect.stringContaining(
`import "example.vue?vue&type=style&index=0&lang.css`
)
)
expect(code).toEqual(
expect.stringContaining(
`import style0 from "example.vue?vue&type=style&index=0&module=custom&lang.css`
)
)
expect(code).toEqual(expect.stringContaining('script.__cssModules = {}'))
expect(code).toEqual(
expect.stringContaining('cssModules["custom"] = style0')
)
})

it.skip('should transform multiple <style module> block', async () => {
await transform(
`<style module>.foo {}</style>
<style module>.bar {}</style>`,
`example.vue`
)
// TODO: Maybe warn about duplicate css module?
})

it('should transform <i18n> block', async () => {
const { code } = await transform(`<i18n>{}</i18n>`, `example.vue`)
expect(code).toEqual(
expect.stringContaining(
`import block0 from "example.vue?vue&type=i18n&index=0&lang.i18n`
)
)
expect(code).toEqual(expect.stringContaining('block0(script)'))
})

it('should transform <i18n lang="json"> block', async () => {
const { code } = await transform(
`<i18n lang="json">{}</i18n>`,
`example.vue`
)
expect(code).toEqual(
expect.stringContaining(
`import block0 from "example.vue?vue&type=i18n&index=0&lang.json`
)
)
expect(code).toEqual(expect.stringContaining('block0(script)'))
})
})
})
73 changes: 69 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export interface Options
target: 'node' | 'browser'
exposeFilename: boolean

customBlocks?: string[]

// if true, handle preprocessors directly instead of delegating to other
// rollup plugins
preprocessStyles?: boolean
Expand All @@ -61,6 +63,7 @@ const defaultOptions: Options = {
exclude: [],
target: 'browser',
exposeFilename: false,
customBlocks: [],
}

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

const filter = createFilter(options.include, options.exclude)
const filterCustomBlock = createCustomBlockFilter(options.customBlocks)

return {
name: 'vue',
Expand All @@ -88,6 +92,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
const [filename] = id.split('?', 2)
cache.set(filename, getDescriptor(importer!))
}
if (!filter(query.filename)) return undefined
debug(`resolveId(${id})`)
return id
}
Expand Down Expand Up @@ -128,6 +133,8 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
async transform(code, id) {
const query = parseVuePartRequest(id)
if (query.vue) {
if (!filter(query.filename)) return null

const descriptor = getDescriptor(query.filename)
const hasScoped = descriptor.styles.some((s) => s.scoped)
if (query.type === 'template') {
Expand Down Expand Up @@ -225,7 +232,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
code,
id,
descriptor,
{ rootContext, isProduction, isServer },
{ rootContext, isProduction, isServer, filterCustomBlock },
options
)
debug('transient .vue file:', '\n' + output + '\n')
Expand All @@ -243,6 +250,27 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
}
}

function createCustomBlockFilter(
queries?: string[]
): (type: string) => boolean {
if (!queries || queries.length === 0) return () => false

const allowed = new Set(queries.filter((query) => /^[a-z]/i.test(query)))
const disallowed = new Set(
queries
.filter((query) => /^![a-z]/i.test(query))
.map((query) => query.substr(1))
)
const allowAll = queries.includes('*') || !queries.includes('!*')

return (type: string) => {
if (allowed.has(type)) return true
if (disallowed.has(type)) return true

return allowAll
}
}

type Query =
| {
filename: string
Expand Down Expand Up @@ -280,7 +308,7 @@ type Query =
}

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

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

Expand Down Expand Up @@ -335,7 +363,13 @@ function transformVueSFC(
rootContext,
isProduction,
isServer,
}: { rootContext: string; isProduction: boolean; isServer: boolean },
filterCustomBlock,
}: {
rootContext: string
isProduction: boolean
isServer: boolean
filterCustomBlock: (type: string) => boolean
},
options: Options
) {
const shortFilePath = relative(rootContext, resourcePath)
Expand All @@ -358,10 +392,16 @@ function transformVueSFC(
id,
options.preprocessStyles
)
const customBlocksCode = getCustomBlock(
descriptor,
resourcePath,
filterCustomBlock
)
const output = [
scriptImport,
templateImport,
stylesCode,
customBlocksCode,
isServer ? `script.ssrRender = ssrRender` : `script.render = render`,
]
if (hasScoped) {
Expand Down Expand Up @@ -429,7 +469,10 @@ function getStyleCode(
// do not include module in default query, since we use it to indicate
// that the module needs to export the modules json
const attrsQuery = attrsToQuery(style.attrs, 'css', preprocessStyles)
const attrsQueryWithoutModule = attrsQuery.replace(/&module(=true)?/, '')
const attrsQueryWithoutModule = attrsQuery.replace(
/&module(=true|=[^&]+)?/,
''
)
// 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}` : ``
Expand Down Expand Up @@ -458,6 +501,28 @@ function getStyleCode(
return stylesCode
}

function getCustomBlock(
descriptor: SFCDescriptor,
resourcePath: string,
filter: (type: string) => boolean
) {
let code = ''

descriptor.customBlocks.forEach((block, index) => {
if (filter(block.type)) {
const src = block.src || resourcePath
const attrsQuery = attrsToQuery(block.attrs, block.type)
const srcQuery = block.src ? `&src` : ``
const query = `?vue&type=${block.type}&index=${index}${srcQuery}${attrsQuery}`
const request = _(src + query)
code += `import block${index} from ${request}\n`
code += `if (typeof block${index} === 'function') block${index}(script)\n`
}
})

return code
}

function createRollupError(id: string, error: CompilerError): RollupError {
return {
id,
Expand Down