@@ -39,6 +39,8 @@ export interface Options
39
39
target : 'node' | 'browser'
40
40
exposeFilename : boolean
41
41
42
+ customBlocks ?: string [ ]
43
+
42
44
// if true, handle preprocessors directly instead of delegating to other
43
45
// rollup plugins
44
46
preprocessStyles ?: boolean
@@ -61,6 +63,7 @@ const defaultOptions: Options = {
61
63
exclude : [ ] ,
62
64
target : 'browser' ,
63
65
exposeFilename : false ,
66
+ customBlocks : [ ] ,
64
67
}
65
68
66
69
export default function PluginVue ( userOptions : Partial < Options > = { } ) : Plugin {
@@ -75,6 +78,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
75
78
const rootContext = process . cwd ( )
76
79
77
80
const filter = createFilter ( options . include , options . exclude )
81
+ const filterCustomBlock = createCustomBlockFilter ( options . customBlocks )
78
82
79
83
return {
80
84
name : 'vue' ,
@@ -88,6 +92,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
88
92
const [ filename ] = id . split ( '?' , 2 )
89
93
cache . set ( filename , getDescriptor ( importer ! ) )
90
94
}
95
+ if ( ! filter ( query . filename ) ) return undefined
91
96
debug ( `resolveId(${ id } )` )
92
97
return id
93
98
}
@@ -128,6 +133,8 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
128
133
async transform ( code , id ) {
129
134
const query = parseVuePartRequest ( id )
130
135
if ( query . vue ) {
136
+ if ( ! filter ( query . filename ) ) return null
137
+
131
138
const descriptor = getDescriptor ( query . filename )
132
139
const hasScoped = descriptor . styles . some ( ( s ) => s . scoped )
133
140
if ( query . type === 'template' ) {
@@ -225,7 +232,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
225
232
code ,
226
233
id ,
227
234
descriptor ,
228
- { rootContext, isProduction, isServer } ,
235
+ { rootContext, isProduction, isServer, filterCustomBlock } ,
229
236
options
230
237
)
231
238
debug ( 'transient .vue file:' , '\n' + output + '\n' )
@@ -243,6 +250,27 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
243
250
}
244
251
}
245
252
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
+
246
274
type Query =
247
275
| {
248
276
filename : string
@@ -280,7 +308,7 @@ type Query =
280
308
}
281
309
282
310
function parseVuePartRequest ( id : string ) : Query {
283
- const [ filename , query ] = id . replace ( / # \. [ \w - ] + $ / , '' ) . split ( '?' , 2 )
311
+ const [ filename , query ] = id . split ( '?' , 2 )
284
312
285
313
if ( ! query ) return { vue : false , filename }
286
314
@@ -335,7 +363,13 @@ function transformVueSFC(
335
363
rootContext,
336
364
isProduction,
337
365
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
+ } ,
339
373
options : Options
340
374
) {
341
375
const shortFilePath = relative ( rootContext , resourcePath )
@@ -358,10 +392,16 @@ function transformVueSFC(
358
392
id ,
359
393
options . preprocessStyles
360
394
)
395
+ const customBlocksCode = getCustomBlock (
396
+ descriptor ,
397
+ resourcePath ,
398
+ filterCustomBlock
399
+ )
361
400
const output = [
362
401
scriptImport ,
363
402
templateImport ,
364
403
stylesCode ,
404
+ customBlocksCode ,
365
405
isServer ? `script.ssrRender = ssrRender` : `script.render = render` ,
366
406
]
367
407
if ( hasScoped ) {
@@ -429,7 +469,10 @@ function getStyleCode(
429
469
// do not include module in default query, since we use it to indicate
430
470
// that the module needs to export the modules json
431
471
const attrsQuery = attrsToQuery ( style . attrs , 'css' , preprocessStyles )
432
- const attrsQueryWithoutModule = attrsQuery . replace ( / & m o d u l e ( = t r u e ) ? / , '' )
472
+ const attrsQueryWithoutModule = attrsQuery . replace (
473
+ / & m o d u l e ( = t r u e | = [ ^ & ] + ) ? / ,
474
+ ''
475
+ )
433
476
// make sure to only pass id when necessary so that we don't inject
434
477
// duplicate tags when multiple components import the same css file
435
478
const idQuery = style . scoped ? `&id=${ id } ` : ``
@@ -458,6 +501,28 @@ function getStyleCode(
458
501
return stylesCode
459
502
}
460
503
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
+
461
526
function createRollupError ( id : string , error : CompilerError ) : RollupError {
462
527
return {
463
528
id,
0 commit comments