1
+ import fs from 'fs' ;
2
+ import { promises as fsPromises } from 'fs' ;
3
+ import htmlparser2 from 'htmlparser2' ;
4
+ import json from '@rollup/plugin-json' ;
5
+ import multiInput from 'rollup-plugin-multi-input' ;
6
+ import { nodeResolve } from '@rollup/plugin-node-resolve' ;
7
+ import path from 'path' ;
8
+ import postcss from 'rollup-plugin-postcss' ;
9
+ import { terser } from 'rollup-plugin-terser' ;
10
+ import ignoreImport from 'rollup-plugin-ignore-import' ;
11
+
12
+ const scratchDirectory = path . join ( process . cwd ( ) , '.greenwood' ) ;
13
+ const workspaceDirectory = path . join ( process . cwd ( ) , 'www' ) ;
14
+ const outputDirectory = path . join ( process . cwd ( ) , 'public' ) ;
15
+
16
+ function greenwoodWorkspaceResolver ( ) {
17
+ return {
18
+ name : 'greenwood-workspace-resolver' , // this name will show up in warnings and errors
19
+ resolveId ( source ) {
20
+ // TODO better way to handle relative paths? happens in generateBundle too
21
+ if ( ( source . indexOf ( './' ) === 0 || source . indexOf ( '/' ) === 0 ) && path . extname ( source ) !== '.html' && fs . existsSync ( path . join ( workspaceDirectory , source ) ) ) {
22
+ const resolvedPath = source . replace ( source , path . join ( workspaceDirectory , source ) ) ;
23
+ // console.debug('resolve THIS sauce to workspace directory, returning ', resolvedPath);
24
+
25
+ return resolvedPath ; // this signals that rollup should not ask other plugins or check the file system to find this id
26
+ }
27
+
28
+ return null ; // other ids should be handled as usually
29
+ }
30
+ } ;
31
+ }
32
+
33
+ // https://github.com/rollup/rollup/issues/2873
34
+ function greenwoodHtmlPlugin ( ) {
35
+
36
+ return {
37
+ name : 'greenwood-html-plugin' ,
38
+ load ( id ) {
39
+ if ( path . extname ( id ) === '.html' ) {
40
+ return '' ;
41
+ }
42
+ } ,
43
+ // TODO do this during load instead?
44
+ async buildStart ( options ) {
45
+ // TODO dont emit duplicate scripts, e.g. use a Map()
46
+ const that = this ;
47
+ const parser = new htmlparser2 . Parser ( {
48
+ onopentag ( name , attribs ) {
49
+ if ( name === 'script' && attribs . type === 'module' && attribs . src ) {
50
+ // TODO handle deeper paths
51
+ const srcPath = attribs . src . replace ( '../' , './' ) ;
52
+ const scriptSrc = fs . readFileSync ( path . join ( workspaceDirectory , srcPath ) , 'utf-8' ) ;
53
+
54
+ that . emitFile ( {
55
+ type : 'chunk' ,
56
+ id : srcPath ,
57
+ name : srcPath . split ( '/' ) [ srcPath . split ( '/' ) . length - 1 ] . replace ( '.js' , '' ) ,
58
+ source : scriptSrc
59
+ } ) ;
60
+
61
+ // console.debug('emitFile for script => ', srcPath);
62
+ }
63
+ }
64
+ } ) ;
65
+
66
+ for ( const input in options . input ) {
67
+ const inputHtml = options . input [ input ] ;
68
+ const html = await fsPromises . readFile ( inputHtml , 'utf-8' ) ;
69
+
70
+ parser . write ( html ) ;
71
+ parser . end ( ) ;
72
+ parser . reset ( ) ;
73
+ }
74
+ } ,
75
+ async generateBundle ( outputOptions , bundles ) {
76
+ const mappedBundles = new Map ( ) ;
77
+
78
+ // TODO looping over bundles twice is wildly inneficient, should refactor and safe references once
79
+ for ( const bundleId of Object . keys ( bundles ) ) {
80
+ const bundle = bundles [ bundleId ] ;
81
+
82
+ // TODO handle (!) Generated empty chunks .greenwood/about, .greenwood/index
83
+ if ( bundle . isEntry && path . extname ( bundle . facadeModuleId ) === '.html' ) {
84
+ const html = await fsPromises . readFile ( bundle . facadeModuleId , 'utf-8' ) ;
85
+ let newHtml = html ;
86
+
87
+ const parser = new htmlparser2 . Parser ( {
88
+ onopentag ( name , attribs ) {
89
+ if ( name === 'script' && attribs . type === 'module' && attribs . src ) {
90
+ // console.debug('bundle', bundle);
91
+ // console.debug(bundles[innerBundleId])
92
+ for ( const innerBundleId of Object . keys ( bundles ) ) {
93
+ const facadeModuleId = bundles [ innerBundleId ] . facadeModuleId ;
94
+ const pathToMatch = attribs . src . replace ( '../' , '' ) . replace ( './' , '' ) ;
95
+
96
+ if ( facadeModuleId && facadeModuleId . indexOf ( pathToMatch ) > 0 ) {
97
+ // console.debug('MATCH FOUND!!!!!!!');
98
+ newHtml = newHtml . replace ( attribs . src , `/${ innerBundleId } ` ) ;
99
+ } else {
100
+ // console.debug('NO MATCH?????', innerBundleId);
101
+ // TODO better testing
102
+ if ( innerBundleId . indexOf ( '.greenwood/' ) < 0 && ! mappedBundles . get ( innerBundleId ) ) {
103
+ // console.debug('NEW BUNDLE TO INJECT!');
104
+ newHtml = newHtml . replace ( / < s c r i p t t y p e = " m o d u l e " s r c = " ( .* ) " > < \/ s c r i p t > / , `
105
+ <script type="module" src="/${ innerBundleId } "></script>
106
+ ` ) ;
107
+ mappedBundles . set ( innerBundleId , true ) ;
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ } ) ;
114
+
115
+ parser . write ( html ) ;
116
+ parser . end ( ) ;
117
+
118
+ // TODO this seems hacky :D
119
+ bundle . fileName = bundle . facadeModuleId . replace ( '.greenwood' , './public' ) ;
120
+ bundle . code = newHtml ;
121
+ }
122
+ }
123
+ }
124
+ } ;
125
+ }
126
+
127
+ export default [ {
128
+ // TODO Avoid .greenwood/ directory, do everything in public/?
129
+ input : `${ scratchDirectory } /**/*.html` ,
130
+ output : {
131
+ dir : outputDirectory ,
132
+ entryFileNames : '[name].[hash].js' ,
133
+ chunkFileNames : '[name].[hash].js'
134
+ } ,
135
+ plugins : [
136
+ // ignoreImport({
137
+ // include: ['**/*.css'],
138
+ // // extensions: ['.css']
139
+ // }),
140
+ nodeResolve ( ) ,
141
+ greenwoodWorkspaceResolver ( ) ,
142
+ greenwoodHtmlPlugin ( ) ,
143
+ multiInput ( ) ,
144
+ postcss ( {
145
+ extract : false ,
146
+ minimize : true
147
+ } ) ,
148
+ json ( ) , // TODO bundle as part of import support?
149
+ terser ( )
150
+ ]
151
+ // }, {
152
+ // input: `${workspaceDirectory}/**/*.css`, // TODO emits a www/styles.js file?
153
+ // output: { // TODO CSS filename hashing / cache busting - https://github.com/egoist/rollup-plugin-postcss/pull/226
154
+ // dir: outputDirectory
155
+ // },
156
+ // plugins: [
157
+ // multiInput(),
158
+ // postcss({
159
+ // extract: true,
160
+ // minimize: true
161
+ // })
162
+ // ]
163
+ } ] ;
0 commit comments