Skip to content

Commit d9e8c6d

Browse files
authored
Standalone build support (#175)
1 parent 4c1e821 commit d9e8c6d

File tree

8 files changed

+97
-19
lines changed

8 files changed

+97
-19
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ Some assorted tips for people who want to fork this project (it's really easy):
5050
- Set the environment variable ENABLE_SERVICE_WORKER=1 to enable service worker for offline support
5151
- Be aware of the license of this project (see below)
5252

53+
## Standalone builds
54+
55+
To make a standalone build, run:
56+
57+
```
58+
NODE_ENV=production STANDALONE=1 npm run build
59+
node src/build/generate-standalone.js
60+
```
61+
62+
Output will be located in dist/standalone.html
63+
5364
## License
5465

5566
Copyright (C) 2021 Thomas Weber

src/build/generate-standalone.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const pathUtil = require('path');
2+
const fs = require('fs');
3+
const glob = require('glob');
4+
5+
const dist = pathUtil.join(__dirname, '..', '..', 'dist');
6+
console.log(`dist: ${dist}`);
7+
8+
const scaffoldingFiles = glob.sync('scaffolding/*.js', {
9+
cwd: dist
10+
});
11+
console.log(`scaffolding: ${scaffoldingFiles.join(', ')}`);
12+
const scaffoldingAssets = {};
13+
for (const path of scaffoldingFiles) {
14+
scaffoldingAssets[path] = fs.readFileSync(pathUtil.join(dist, path), 'utf-8');
15+
}
16+
17+
const indexPath = pathUtil.join(dist, 'index.html');
18+
console.log(`index.html: ${indexPath}`);
19+
let indexContent = fs.readFileSync(indexPath, 'utf8');
20+
const jsPath = pathUtil.join(dist, indexContent.match(/<script src="(.*)"><\/script>/)[1]);
21+
console.log(`packager.js: ${jsPath}`);
22+
let jsContent = fs.readFileSync(jsPath, 'utf-8');
23+
jsContent = `window.__ASSETS__=${JSON.stringify(scaffoldingAssets)};${jsContent}`;
24+
jsContent = jsContent.replace(/<\/script>/g, '\\u003c/script>');
25+
indexContent = indexContent.replace(/<script src=".*"><\/script>/, () => `<script>${jsContent}</script>`);
26+
27+
const standalonePath = pathUtil.join(dist, 'standalone.html');
28+
console.log(`standalone.html: ${standalonePath}`);
29+
fs.writeFileSync(standalonePath, indexContent);

src/build/p4-worker-loader.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@ module.exports.pitch = function (request) {
2121
compiler.runAsChild((err, entries, compilation) => {
2222
if (err) return callback(err);
2323
const file = entries[0].files[0];
24+
const inline = !!process.env.STANDALONE;
2425
// extra whitespace here won't matter
2526
const source = `
2627
import {wrap} from 'comlink';
2728
const createWorker = () => {
28-
const worker = new Worker(__webpack_public_path__ + ${JSON.stringify(file)});
29+
${inline ? `const source = ${JSON.stringify(compilation.assets[file].source())};
30+
const blob = new Blob([source]);
31+
const url = URL.createObjectURL(blob);
32+
const worker = new Worker(url);
33+
URL.revokeObjectURL(url);` : `const worker = new Worker(__webpack_public_path__ + ${JSON.stringify(file)});`}
2934
const terminate = () => {
3035
worker.terminate();
3136
};

src/build/standalone-plugin.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Plugin to modify webpack to interpret all dynamic imports as webpackMode: "eager"
2+
3+
const patchParser = (parser) => {
4+
const originalParseCommentOptions = parser.parseCommentOptions;
5+
parser.parseCommentOptions = function (...args) {
6+
const result = originalParseCommentOptions.call(this, ...args);
7+
result.options.webpackMode = 'eager';
8+
return result;
9+
};
10+
};
11+
12+
class TWStandalonePlugin {
13+
apply (compiler) {
14+
compiler.hooks.normalModuleFactory.tap('TWStandalonePlugin', (normalModuleFactory) => {
15+
normalModuleFactory.hooks.parser.for('javascript/auto').tap('TWStandalonePlugin', patchParser);
16+
normalModuleFactory.hooks.parser.for('javascript/dynamic').tap('TWStandalonePlugin', patchParser);
17+
normalModuleFactory.hooks.parser.for('javascript/esm').tap('TWStandalonePlugin', patchParser);
18+
});
19+
}
20+
}
21+
22+
module.exports = TWStandalonePlugin;

src/p4/preview.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const origin = process.env.STANDALONE ? '*' : location.origin;
2+
13
const source = `<!DOCTYPE html>
24
<html>
35
<head>
@@ -50,6 +52,7 @@ const source = `<!DOCTYPE html>
5052
</div>
5153
<script>
5254
(function() {
55+
const origin = ${JSON.stringify(origin)};
5356
const err = (message) => {
5457
document.querySelector(".preview-error").hidden = false;
5558
document.querySelector(".preview-error-message").textContent = "Error: " + message;
@@ -62,7 +65,7 @@ const source = `<!DOCTYPE html>
6265
const progressBar = document.querySelector(".preview-progress-inner");
6366
const progressText = document.querySelector(".preview-progress-text");
6467
window.addEventListener("message", (e) => {
65-
if (e.origin !== location.origin) return;
68+
if (origin !== "*" && e.origin !== location.origin) return;
6669
if (hasRun) return;
6770
if (e.data.blob) {
6871
hasRun = true;
@@ -83,7 +86,7 @@ const source = `<!DOCTYPE html>
8386
});
8487
window.opener.postMessage({
8588
preview: "hello"
86-
}, location.origin);
89+
}, origin);
8790
})();
8891
</script>
8992
</body>
@@ -107,14 +110,14 @@ class Preview {
107110
windowToBlobMap.set(this.window, content);
108111
this.window.postMessage({
109112
blob: content
110-
}, location.origin);
113+
}, origin);
111114
}
112115

113116
setProgress (progress, text) {
114117
this.window.postMessage({
115118
progress,
116119
text
117-
}, location.origin);
120+
}, origin);
118121
}
119122

120123
close () {
@@ -123,7 +126,7 @@ class Preview {
123126
}
124127

125128
window.addEventListener('message', (e) => {
126-
if (e.origin !== location.origin) {
129+
if (origin !== '*' && e.origin !== location.origin) {
127130
return;
128131
}
129132
const data = e.data;
@@ -133,7 +136,7 @@ window.addEventListener('message', (e) => {
133136
if (blob) {
134137
source.postMessage({
135138
blob
136-
}, location.origin);
139+
}, origin);
137140
}
138141
}
139142
});

src/p4/template.ejs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
<head>
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
6-
<link rel="shortcut icon" href="/favicon.ico?v=2" />
76
<% const {LONG_NAME, WEBSITE} = require('../packager/brand.js'); %>
87
<title><%= LONG_NAME %></title>
98
<meta name="description" content="Converts Scratch projects into HTML files, zip archives, or executable programs for Windows, macOS, and Linux.">
10-
<meta property="og:type" content="website" />
11-
<meta property="og:title" content="<%= LONG_NAME %>" />
12-
<meta property="og:description" content="Converts Scratch projects into HTML files, zip archives, or executable programs for Windows, macOS, and Linux." />
13-
<meta property="og:url" content="<%= WEBSITE %>" />
9+
<% if (!process.env.STANDALONE) { %>
10+
<link rel="shortcut icon" href="/favicon.ico?v=2" />
11+
<meta property="og:type" content="website" />
12+
<meta property="og:title" content="<%= LONG_NAME %>" />
13+
<meta property="og:description" content="Converts Scratch projects into HTML files, zip archives, or executable programs for Windows, macOS, and Linux." />
14+
<meta property="og:url" content="<%= WEBSITE %>" />
15+
<% } %>
1416
<style>
1517
body[p4-splash-theme="dark"]:not([p4-loaded]) {
1618
background-color: #111;

src/packager/packager.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ class Packager extends EventTarget {
195195
if (!asset) {
196196
throw new Error(`Invalid asset: ${name}`);
197197
}
198+
if (process.env.STANDALONE && window.__ASSETS__ && window.__ASSETS__[asset.src]) {
199+
return window.__ASSETS__[asset.src];
200+
}
198201
const dispatchProgress = (progress) => this.dispatchEvent(new CustomEvent('large-asset-fetch', {
199202
detail: {
200203
asset: name,

webpack.config.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl
55
const CopyWebpackPlugin = require('copy-webpack-plugin');
66
const AddBuildIDToOutputPlugin = require('./src/build/add-build-id-to-output-plugin');
77
const GenerateServiceWorkerPlugin = require('./src/build/generate-service-worker-plugin');
8+
const StandalonePlugin = require('./src/build/standalone-plugin');
89

910
const isProduction = process.env.NODE_ENV === 'production';
1011
const base = {
@@ -119,14 +120,14 @@ const makeWebsite = () => ({
119120
rules: [
120121
{
121122
test: /\.png$/i,
122-
use: [
123-
{
124-
loader: 'file-loader',
125-
options: {
126-
name: 'assets/[name].[contenthash].[ext]'
127-
}
123+
use: process.env.STANDALONE ? {
124+
loader: 'url-loader'
125+
} : {
126+
loader: 'file-loader',
127+
options: {
128+
name: 'assets/[name].[contenthash].[ext]'
128129
}
129-
]
130+
}
130131
},
131132
{
132133
test: /\.(html|svelte)$/,
@@ -146,6 +147,7 @@ const makeWebsite = () => ({
146147
'process.env.SCAFFOLDING_BUILD_ID': buildId ? JSON.stringify(buildId) : 'Math.random().toString()',
147148
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
148149
'process.env.ENABLE_SERVICE_WORKER': JSON.stringify(process.env.ENABLE_SERVICE_WORKER),
150+
'process.env.STANDALONE': JSON.stringify(process.env.STANDALONE),
149151
'process.env.PLAUSIBLE_API': JSON.stringify(process.env.PLAUSIBLE_API),
150152
'process.env.PLAUSIBLE_DOMAIN': JSON.stringify(process.env.PLAUSIBLE_DOMAIN),
151153
}),
@@ -155,6 +157,7 @@ const makeWebsite = () => ({
155157
chunks: ['packager']
156158
}),
157159
new GenerateServiceWorkerPlugin(),
160+
...(process.env.STANDALONE ? [new StandalonePlugin()] : []),
158161
...(process.env.BUNDLE_ANALYZER === '3' ? [new BundleAnalyzerPlugin()] : [])
159162
],
160163
devServer: {

0 commit comments

Comments
 (0)