@@ -47,10 +47,19 @@ const EXAMPLE_SOURCES = [
4747 } ,
4848] ;
4949
50- function buildHtml ( exampleName , bundleFile , isModule = false ) {
51- const exampleScript = isModule
52- ? `<script type="module" src="${ bundleFile } "></script>`
53- : `<script src="${ bundleFile } "></script>` ;
50+ function buildHtml (
51+ exampleName ,
52+ bundleFile ,
53+ isModule = false ,
54+ inlineScript = null
55+ ) {
56+ let exampleScript = `<script src="${ bundleFile } "></script>` ;
57+ if ( isModule ) {
58+ exampleScript = `<script type="module" src="${ bundleFile } "></script>` ;
59+ }
60+ if ( inlineScript ) {
61+ exampleScript = `<script>${ inlineScript } </script>` ;
62+ }
5463
5564 return `<!doctype html>
5665<html lang="en">
@@ -112,16 +121,37 @@ async function collectEntries() {
112121 return entries ;
113122}
114123
115- function assetLoader ( ) {
124+ function getMimeType ( filePath ) {
125+ const ext = path . extname ( filePath ) . toLowerCase ( ) ;
126+ const mimeByExt = {
127+ '.png' : 'image/png' ,
128+ '.jpg' : 'image/jpeg' ,
129+ '.jpeg' : 'image/jpeg' ,
130+ '.gif' : 'image/gif' ,
131+ '.svg' : 'image/svg+xml' ,
132+ '.webp' : 'image/webp' ,
133+ } ;
134+ return mimeByExt [ ext ] || 'application/octet-stream' ;
135+ }
136+
137+ function toDataUri ( filePath , source ) {
138+ return `data:${ getMimeType ( filePath ) } ;base64,${ source . toString ( 'base64' ) } ` ;
139+ }
140+
141+ function assetLoader ( { inline = false } = { } ) {
116142 const assetRegex = / \. ( p n g | j p e ? g ) $ / i;
143+
117144 return {
118- name : 'asset-loader' ,
145+ name : inline ? 'asset-loader-inline' : 'asset-loader' ,
119146 async load ( id ) {
120147 if ( ! assetRegex . test ( id ) ) {
121148 return null ;
122149 }
123150
124151 const source = await fs . readFile ( id ) ;
152+ if ( inline ) {
153+ return `export default ${ JSON . stringify ( toDataUri ( id , source ) ) } ;` ;
154+ }
125155 const refId = this . emitFile ( {
126156 type : 'asset' ,
127157 name : path . basename ( id ) ,
@@ -153,6 +183,60 @@ function replaceBasePath(basePath) {
153183 } ;
154184}
155185
186+ function inlineCssUrls ( ) {
187+ const cssFileRegex = / \. c s s $ / i;
188+ const urlRegex = / u r l \( ( [ ' " ] ? ) ( [ ^ ' " ) ] + ) \1\) / g;
189+ return {
190+ name : 'inline-css-urls' ,
191+ async transform ( code , id ) {
192+ if ( ! cssFileRegex . test ( id ) ) {
193+ return null ;
194+ }
195+
196+ const matches = Array . from ( code . matchAll ( urlRegex ) ) ;
197+ if ( ! matches . length ) {
198+ return null ;
199+ }
200+
201+ const replacements = await Promise . all (
202+ matches . map ( async ( [ fullMatch , quote , rawUrl ] ) => {
203+ const assetUrl = rawUrl . trim ( ) ;
204+ if (
205+ assetUrl . startsWith ( 'data:' ) ||
206+ assetUrl . startsWith ( 'http://' ) ||
207+ assetUrl . startsWith ( 'https://' ) ||
208+ assetUrl . startsWith ( '//' )
209+ ) {
210+ return { fullMatch, replacement : fullMatch } ;
211+ }
212+
213+ const assetPath = path . resolve ( path . dirname ( id ) , assetUrl ) ;
214+ try {
215+ const source = await fs . readFile ( assetPath ) ;
216+ const dataUrl = toDataUri ( assetPath , source ) ;
217+ return {
218+ fullMatch,
219+ replacement : `url(${ quote } ${ dataUrl } ${ quote } )` ,
220+ } ;
221+ } catch ( err ) {
222+ return { fullMatch, replacement : fullMatch } ;
223+ }
224+ } )
225+ ) ;
226+
227+ let transformed = code ;
228+ replacements . forEach ( ( { fullMatch, replacement } ) => {
229+ transformed = transformed . replace ( fullMatch , replacement ) ;
230+ } ) ;
231+
232+ return {
233+ code : transformed ,
234+ map : null ,
235+ } ;
236+ } ,
237+ } ;
238+ }
239+
156240async function walkFiles ( dir , results = [ ] ) {
157241 const entries = await fs . readdir ( dir , { withFileTypes : true } ) ;
158242 await Promise . all (
@@ -169,15 +253,15 @@ async function walkFiles(dir, results = []) {
169253}
170254
171255async function copyApplicationStaticAssets ( entryPath , outDir ) {
172- const appDir = path . dirname ( entryPath ) ;
256+ const sourceDir = path . dirname ( entryPath ) ;
173257 const assetRegex = / \. ( p n g | j p e ? g | g i f | s v g | w e b p ) $ / i;
174- const files = await walkFiles ( appDir ) ;
258+ const files = await walkFiles ( sourceDir ) ;
175259
176260 await Promise . all (
177261 files
178262 . filter ( ( filePath ) => assetRegex . test ( filePath ) )
179263 . map ( async ( filePath ) => {
180- const relPath = path . relative ( appDir , filePath ) ;
264+ const relPath = path . relative ( sourceDir , filePath ) ;
181265 const destPath = path . resolve ( outDir , relPath ) ;
182266 await fs . mkdir ( path . dirname ( destPath ) , { recursive : true } ) ;
183267 await fs . copyFile ( filePath , destPath ) ;
@@ -205,7 +289,7 @@ async function build() {
205289 const stringPlugin = string . default ? string . default : string ;
206290 const ignorePlugin = ignore . default ? ignore . default : ignore ;
207291
208- function createPlugins ( ) {
292+ function createPlugins ( { forInlineIife = false } = { } ) {
209293 return [
210294 aliasPlugin ( {
211295 entries : [
@@ -217,6 +301,7 @@ async function build() {
217301 ] ,
218302 } ) ,
219303 ignorePlugin ( [ 'crypto' ] ) ,
304+ ...( forInlineIife ? [ inlineCssUrls ( ) ] : [ ] ) ,
220305 webworkerPlugin ( {
221306 targetPlatform : 'browser' ,
222307 pattern : / ^ .+ \. w o r k e r (?: \. j s ) ? $ / ,
@@ -250,7 +335,7 @@ async function build() {
250335 } ,
251336 plugins : [ autoprefixer ] ,
252337 } ) ,
253- assetLoader ( ) ,
338+ assetLoader ( { inline : forInlineIife } ) ,
254339 replaceBasePath ( '/vtk-js' ) ,
255340 ] ;
256341 }
@@ -282,6 +367,14 @@ async function build() {
282367 assetFileNames : '_assets/[name]-[hash][extname]' ,
283368 } ) ;
284369 await esBundle . close ( ) ;
370+
371+ await Promise . all (
372+ Object . entries ( esEntries ) . map ( async ( [ chunkName , entryPath ] ) => {
373+ const outDir = path . resolve ( distDir , chunkName ) ;
374+ await fs . mkdir ( outDir , { recursive : true } ) ;
375+ await copyApplicationStaticAssets ( entryPath , outDir ) ;
376+ } )
377+ ) ;
285378 }
286379
287380 await Object . entries ( applicationEntries ) . reduce (
@@ -292,18 +385,28 @@ async function build() {
292385 await fs . mkdir ( outDir , { recursive : true } ) ;
293386 const bundle = await rollup ( {
294387 input : entryPath ,
295- plugins : createPlugins ( ) ,
388+ plugins : createPlugins ( { forInlineIife : true } ) ,
296389 } ) ;
297- await bundle . write ( {
298- file : path . resolve ( outDir , 'index.js' ) ,
390+ const { output } = await bundle . generate ( {
299391 format : 'iife' ,
300392 name : `${ chunkName . replace ( / [ ^ \w $ ] / g, '_' ) } ` ,
301393 inlineDynamicImports : true ,
302- assetFileNames : '_assets/[name]-[hash][extname]' ,
303394 } ) ;
304395 await bundle . close ( ) ;
305396
306- await copyApplicationStaticAssets ( entryPath , outDir ) ;
397+ const appChunk = output . find ( ( item ) => item . type === 'chunk' ) ;
398+ if ( ! appChunk ) {
399+ throw new Error ( `Failed to generate inline IIFE for ${ chunkName } ` ) ;
400+ }
401+ const inlineScript = appChunk . code . replace (
402+ / < \/ s c r i p t > / gi,
403+ '<\\/script>'
404+ ) ;
405+ await fs . writeFile (
406+ path . resolve ( outDir , 'index.html' ) ,
407+ buildHtml ( chunkName , './index.js' , false , inlineScript ) ,
408+ 'utf8'
409+ ) ;
307410 } ) ,
308411 Promise . resolve ( )
309412 ) ;
@@ -313,6 +416,9 @@ async function build() {
313416 const outDir = path . resolve ( distDir , chunkName ) ;
314417 const bundleFile = './index.js' ;
315418 const isModule = ! Object . hasOwn ( applicationEntries , chunkName ) ;
419+ if ( ! isModule ) {
420+ return ;
421+ }
316422
317423 await fs . mkdir ( outDir , { recursive : true } ) ;
318424 await fs . writeFile (
0 commit comments