1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-disable no-console */
const fs = require("fs");
const { mkdir } = require("shelljs");
const path = require("path");
const { pathToFileURL } = require("url");
const chalk = require("chalk");
const DEFAULT_OPTIONS = {
// Glob leading from CWD to the parent of the intended prerendered directory.
// Starting in newtab/bin/ and we want to write to newtab/prerendered/ so we
// go up one level.
addonPath: "..",
// depends on the registration in browser/components/newtab/jar.mn
baseUrl: "resource://activity-stream/",
};
/**
* templateHTML - Generates HTML for activity stream, given some options and
* prerendered HTML if necessary.
*
* @param {obj} options
* {str} options.baseUrl The base URL for all local assets
* {bool} options.debug Should we use dev versions of JS libraries?
* {bool} options.noscripts Should we include scripts in the prerendered files?
* @return {str} An HTML document as a string
*/
function templateHTML(options) {
const debugString = options.debug ? "-dev" : "";
// This list must match any similar ones in AboutNewTabChild.sys.mjs
const scripts = [
"chrome://browser/content/contentSearchUI.js",
"chrome://browser/content/contentSearchHandoffUI.js",
"chrome://browser/content/contentTheme.js",
`${options.baseUrl}vendor/react${debugString}.js`,
`${options.baseUrl}vendor/react-dom${debugString}.js`,
`${options.baseUrl}vendor/prop-types.js`,
`${options.baseUrl}vendor/redux.js`,
`${options.baseUrl}vendor/react-redux.js`,
`${options.baseUrl}vendor/react-transition-group.js`,
`${options.baseUrl}data/content/activity-stream.bundle.js`,
`${options.baseUrl}data/content/newtab-render.js`,
];
// Add spacing and script tags
const scriptRender = `\n${scripts
.map(script => ` <script src="${script}"></script>`)
.join("\n")}`;
// The markup below needs to be formatted by Prettier. But any diff after
// running this script should be caught by try-runnner.js
return `
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this file,
- You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; object-src 'none'; script-src resource: chrome:; connect-src https:; img-src https: data: blob: chrome:; style-src 'unsafe-inline';"
/>
<meta name="color-scheme" content="light dark" />
<title data-l10n-id="newtab-page-title"></title>
<link
rel="icon"
type="image/png"
href="chrome://branding/content/icon32.png"
/>
<link rel="localization" href="branding/brand.ftl" />
<link rel="localization" href="toolkit/branding/brandings.ftl" />
<link rel="localization" href="browser/newtab/newtab.ftl" />
<link
rel="stylesheet"
href="chrome://global/skin/design-system/tokens-brand.css"
/>
<link
rel="stylesheet"
href="chrome://browser/content/contentSearchUI.css"
/>
<link
rel="stylesheet"
href="chrome://activity-stream/content/css/activity-stream.css"
/>
</head>
<body class="activity-stream">
<div id="root"></div>${options.noscripts ? "" : scriptRender}
<script
async
type="module"
src="chrome://global/content/elements/moz-toggle.mjs"
></script>
</body>
</html>
`.trimLeft();
}
/**
* writeFiles - Writes to the desired files the result of a template given
* various prerendered data and options.
*
* @param {string} destPath Path to write the files to
* @param {Map} filesMap Mapping of a string file name to templater
* @param {Object} options Various options for the templater
*/
function writeFiles(destPath, filesMap, options) {
for (const [file, templater] of filesMap) {
fs.writeFileSync(path.join(destPath, file), templater({ options }));
console.log(chalk.green(`✓ ${file}`));
}
}
const STATIC_FILES = new Map([
["activity-stream.html", ({ options }) => templateHTML(options)],
[
"activity-stream-debug.html",
({ options }) => templateHTML(Object.assign({}, options, { debug: true })),
],
[
"activity-stream-noscripts.html",
({ options }) =>
templateHTML(Object.assign({}, options, { noscripts: true })),
],
]);
/**
* main - Parses command line arguments, generates html and js with templates,
* and writes files to their specified locations.
*/
async function main() {
const { default: meow } = await import("meow");
const fileUrl = pathToFileURL(__filename);
const cli = meow(
`
Usage
$ node ./bin/render-activity-stream-html.js [options]
Options
-a PATH, --addon-path PATH Path to the parent of the target directory.
default: "${DEFAULT_OPTIONS.addonPath}"
-b URL, --base-url URL Base URL for assets.
default: "${DEFAULT_OPTIONS.baseUrl}"
--help Show this help message.
`,
{
description: false,
// `pkg` is a tiny optimization. It prevents meow from looking for a package
// that doesn't technically exist. meow searches for a package and changes
// the process name to the package name. It resolves to the newtab
// package.json, which would give a confusing name and be wasteful.
pkg: {
name: "render-activity-stream-html",
version: "0.0.0",
},
// `importMeta` is required by meow 10+. It was added to support ESM, but
// meow now requires it, and no longer supports CJS style imports. But it
// only uses import.meta.url, which can be polyfilled like this:
importMeta: { url: fileUrl },
flags: {
addonPath: {
type: "string",
alias: "a",
default: DEFAULT_OPTIONS.addonPath,
},
baseUrl: {
type: "string",
alias: "b",
default: DEFAULT_OPTIONS.baseUrl,
},
},
}
);
const options = Object.assign({ debug: false }, cli.flags || {});
const addonPath = path.resolve(__dirname, options.addonPath);
const prerenderedPath = path.join(addonPath, "prerendered");
console.log(`Writing prerendered files to ${prerenderedPath}:`);
mkdir("-p", prerenderedPath);
writeFiles(prerenderedPath, STATIC_FILES, options);
}
main();
|