11import { promises as fs } from 'fs' ;
2- import { attachScopes } from '@rollup/pluginutils' ;
3- import { walk } from 'estree-walker' ;
4- import isReference from 'is-reference' ;
5-
6- const gmAPIs = [
7- 'GM_info' ,
8- 'GM_getValue' ,
9- 'GM_setValue' ,
10- 'GM_deleteValue' ,
11- 'GM_listValues' ,
12- 'GM_addValueChangeListener' ,
13- 'GM_removeValueChangeListener' ,
14- 'GM_getResourceText' ,
15- 'GM_getResourceURL' ,
16- 'GM_addStyle' ,
17- 'GM_openInTab' ,
18- 'GM_registerMenuCommand' ,
19- 'GM_unregisterMenuCommand' ,
20- 'GM_notification' ,
21- 'GM_setClipboard' ,
22- 'GM_xmlhttpRequest' ,
23- 'GM_download' ,
24- ] ;
25- const META_START = '// ==UserScript==' ;
26- const META_END = '// ==/UserScript==' ;
2+ import { collectGmApi , getMetadata } from './util' ;
273
284export default ( metafile , transform ) => {
295 const grantMap = new Map ( ) ;
306 return {
31- name : 'banner ' ,
7+ name : 'userscript-metadata ' ,
328 buildStart ( ) {
339 this . addWatchFile ( metafile ) ;
3410 } ,
3511 transform ( code , id ) {
3612 const ast = this . parse ( code ) ;
37- let scope = attachScopes ( ast , 'scope' ) ;
38- const grantSetPerFile = new Set ( ) ;
39- walk ( ast , {
40- enter ( node , parent ) {
41- if ( node . scope ) scope = node . scope ;
42- if ( node . type === 'Identifier' && isReference ( node , parent ) && ! scope . contains ( node . name ) ) {
43- if ( gmAPIs . includes ( node . name ) ) {
44- grantSetPerFile . add ( node . name ) ;
45- }
46- }
47- } ,
48- leave ( node ) {
49- if ( node . scope ) scope = scope . parent ;
50- } ,
51- } ) ;
13+ const grantSetPerFile = collectGmApi ( ast ) ;
5214 grantMap . set ( id , grantSetPerFile ) ;
5315 } ,
54- async banner ( ) {
55- let meta = await fs . readFile ( metafile , 'utf8' ) ;
56- const lines = meta . split ( '\n' ) . map ( line => line . trim ( ) ) ;
57- const start = lines . indexOf ( META_START ) ;
58- const end = lines . indexOf ( META_END ) ;
59- if ( start < 0 || end < 0 ) {
60- console . warn ( 'Invalid metadata block. For more details see https://violentmonkey.github.io/api/metadata-block/' ) ;
61- return ;
62- }
16+ /**
17+ * Use `renderChunk` instead of `banner` to preserve the metadata after minimization.
18+ * Note that this plugin must be put after `@rollup/plugin-terser`.
19+ */
20+ async renderChunk ( code ) {
21+ const meta = await fs . readFile ( metafile , 'utf8' ) ;
6322 const grantSet = new Set ( ) ;
64- const items = lines . slice ( start + 1 , end )
65- . map ( line => {
66- if ( ! line . startsWith ( '// ' ) ) return ;
67- line = line . slice ( 3 ) . trim ( ) ;
68- const i = line . indexOf ( ' ' ) ;
69- if ( i < 0 ) return ;
70- const key = line . slice ( 0 , i ) ;
71- const value = line . slice ( i + 1 ) . trim ( ) ;
72- if ( key === '@grant' ) {
73- grantSet . add ( value ) ;
74- return ;
75- }
76- return [ key , value ] ;
77- } )
78- . filter ( Boolean ) ;
7923 for ( const id of this . getModuleIds ( ) ) {
8024 const grantSetPerFile = grantMap . get ( id ) ;
8125 if ( grantSetPerFile ) {
@@ -84,20 +28,9 @@ export default (metafile, transform) => {
8428 }
8529 }
8630 }
87- const grantList = Array . from ( grantSet ) ;
88- grantList . sort ( ) ;
89- for ( const item of grantList ) {
90- items . push ( [ '@grant' , item ] ) ;
91- }
92- const maxKeyWidth = Math . max ( ...items . map ( ( [ key ] ) => key . length ) ) ;
93- meta = [
94- META_START ,
95- ...items . map ( ( [ key , value ] ) => `// ${ key . padEnd ( maxKeyWidth ) } ${ value } ` ) ,
96- META_END ,
97- '' ,
98- ] . join ( '\n' ) ;
99- if ( transform ) meta = transform ( meta ) ;
100- return meta ;
31+ let metadata = getMetadata ( meta , grantSet ) ;
32+ if ( transform ) metadata = transform ( metadata ) ;
33+ return `${ metadata } \n\n${ code } ` ;
10134 } ,
10235 } ;
10336} ;
0 commit comments