-
Notifications
You must be signed in to change notification settings - Fork 494
Expand file tree
/
Copy pathrollup.config.mjs
More file actions
288 lines (268 loc) · 10.5 KB
/
Copy pathrollup.config.mjs
File metadata and controls
288 lines (268 loc) · 10.5 KB
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import { nodeResolve } from '@rollup/plugin-node-resolve';
import zlib from 'node:zlib';
import visualizer from 'rollup-plugin-visualizer';
import commonjs from '@rollup/plugin-commonjs';
import replace from "@rollup/plugin-replace";
import terser from "@rollup/plugin-terser";
import copy from "rollup-plugin-copy";
import typescript from "@rollup/plugin-typescript";
import fs from 'fs';
import path from 'path';
import postprocess from '@zsviczian/rollup-plugin-postprocess';
import cssnano from 'cssnano';
import { minify } from 'uglify-js';
import json from '@rollup/plugin-json';
import { parseEnv } from 'node:util';
import { buildReactRuntime } from './scripts/buildReactRuntime.mjs';
function compressDeflateBase64(code) {
// Compress using Node's native zlib at maximum compression
const compressed = zlib.deflateSync(Buffer.from(code, "utf-8"), { level: 9 });
return compressed.toString("base64");
}
try {
const envContent = fs.readFileSync(path.resolve('.env'), 'utf8');
Object.assign(process.env, parseEnv(envContent));
} catch (error) {
}
const DIST_FOLDER = 'dist';
const absolutePath = path.resolve(DIST_FOLDER);
fs.mkdirSync(absolutePath, { recursive: true });
const isProd = (process.env.NODE_ENV === "production");
const isLib = (process.env.NODE_ENV === "lib");
console.log(`Running: ${process.env.NODE_ENV}; isProd: ${isProd}; isLib: ${isLib}`);
// Add non-English locales here to embed them as compressed payloads in main.js.
// When adding a locale file:
// 1) add its code to this list, 2) build once, 3) if build fails because the locale
const LANGUAGES = ['ru', 'zh-cn', 'zh-tw', 'es']; //english is not compressed as it is always loaded by default
function trimLastSemicolon(input) {
if (input.endsWith(";")) {
return input.slice(0, -1);
}
return input;
}
function minifyCode(code) {
const minified = minify(code, {
compress: {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2170
reduce_vars: false,
},
mangle: true,
output: {
comments: false,
beautify: false,
}
});
if (minified.error) {
throw new Error(minified.error);
}
return minified.code;
}
function compressLanguageFile(lang) {
const inputDir = "./src/lang/locale";
const filePath = `${inputDir}/${lang}.ts`;
let content = fs.readFileSync(filePath, "utf-8");
content = trimLastSemicolon(content.split("export default")[1].trim());
return compressDeflateBase64(minifyCode(`x = ${content};`));
}
const excalidrawSource = isLib ? "" : fs.readFileSync(
isProd
? "./node_modules/@zsviczian/excalidraw/dist/obsidian/excalidraw.production.min.js"
: "./node_modules/@zsviczian/excalidraw/dist/obsidian/excalidraw.development.js",
"utf8",
);
const excalidraw_pkg = isLib
? ""
: isProd
? minifyCode(excalidrawSource)
: `${excalidrawSource}\n//# sourceURL=obsidian-excalidraw-runtime.development.js\n`;
const reactRuntimeSource = isLib
? ""
: await buildReactRuntime({ isProduction: isProd });
const reactRuntime = isLib || !isProd
? reactRuntimeSource
: minifyCode(reactRuntimeSource);
const reactPackagesCompressed = isLib
? ""
: compressDeflateBase64(reactRuntime);
// Runtime payloads are only decompressed; including Pako's deflate implementation
// would add unused code to the size-constrained Obsidian plugin bundle.
const pako_pkg = isLib ? "" : fs.readFileSync("./node_modules/pako/dist/pako_inflate.min.js", "utf8");
if (!isLib) {
const excalidraw_styles = isProd
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/obsidian/excalidraw.production.min.css", "utf8")
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/obsidian/excalidraw.development.css", "utf8");
const plugin_styles = fs.readFileSync("./styles.css", "utf8");
const styles = excalidraw_styles + plugin_styles;
cssnano()
.process(styles, {
from: path.resolve("styles.css"),
to: path.resolve(DIST_FOLDER, "styles.css"),
})
.then(result => {
fs.writeFileSync(`./${DIST_FOLDER}/styles.css`, result.css);
})
.catch(error => {
console.error('Error while processing CSS:', error);
});
}
const manifestStr = isLib ? "" : fs.readFileSync("manifest-beta.json", "utf-8");
const manifest = isLib ? {} : JSON.parse(manifestStr);
if (!isLib) {
console.log(manifest.version);
}
const packageString = isLib
? ""
: ';const INITIAL_TIMESTAMP=Date.now();\n' +
'const pako = (function() {\n' +
' const module = { exports: {} };\n' +
' const exports = module.exports;\n' +
' ' + pako_pkg + '\n' +
' return module.exports;\n' +
'})();\n' +
// Define the dependency-free inflater before React participates in bootstrap.
'const unpackBase64Deflate = (b64) => {\n' +
' const binStr = atob(b64);\n' +
' const len = binStr.length;\n' +
' const bytes = new Uint8Array(len);\n' +
' for (let i = 0; i < len; i++) bytes[i] = binStr.charCodeAt(i);\n' +
' return new TextDecoder().decode(pako.inflate(bytes));\n' +
'};\n' +
'window.unpackBase64Deflate = unpackBase64Deflate;\n' +
'let REACT_PACKAGES = unpackBase64Deflate("' + reactPackagesCompressed + '");\n' +
'const unpackExcalidraw = () => unpackBase64Deflate("' + compressDeflateBase64(excalidraw_pkg) + '");\n' +
'let {React, ReactDOM, ReactJSXRuntime, ReactJSXDevRuntime} = new Function(`${REACT_PACKAGES}; return {React, ReactDOM, ReactJSXRuntime, ReactJSXDevRuntime};`)();\n' +
'let react = React;\n' +
'let reactDOM = ReactDOM;\n' +
'let excalidrawLib = {};\n' +
`const PLUGIN_LANGUAGES = {${LANGUAGES.map(lang => `"${lang}": "${compressLanguageFile(lang)}"`).join(",")}};\n` +
//These declarations were moved here because Obsidian code scanner incorrectly flags them with a warning,
//but without offering a proper resolution, or a stepout process to remove them from scanner results.
//for context you can read different issues I raised with the Obsidian team about these.
//Once there is a workable resolution I am moving them back to their original locations, since having them here
//is not at all ideal.
//https://github.com/obsidianmd/eslint-plugin/issues/175
`const getCaretRangeFromPoint = (doc, x, y) => doc.caretRangeFromPoint?.(x, y);\n` +
//There isn't a process for flagging deliberate use of main document instead of activeDocument
//The blanket rule by the eslint-plugin makes sense for many cases, but does not address special case needs
`const mainDocument = document;\n` +
//Fetch is the only valid approach in case of loading binary data such as fonts to dataURL (i.e. not network related)
//I've also had cases in the past where requestUrl failed with certain endpoints, for those cases I have
//fetch in the codebase as a fallback from requestUrl.
//https://github.com/obsidianmd/eslint-plugin/issues/176
`const deliberateFetch = async (payload, init) => await fetch(payload, init);\n` +
`const PLUGIN_VERSION="${manifest.version}";\n` +
//Moved here since the Obsidian code scanner warning to avoid unnecessary logging appears
//to users, creating the impression that there is unnecessary logging. There isn't.
//Errors and debug information is logged. Nothing else.
`const consoleLog = console["log"].bind(console);\n` +
//Obsidian code scanner fails if document.createElement rule is ignored using the eslint-ignore comment
//the code scanner also does not allow creating style elements and guides plugins to use style.css
//the code scanner does not recognize valid cases such as creating canvas elements for image generation
//adding a style element to an iframe that is used to create an image
//I am sorry, but I got fatigued creating issues for all my edge cases with the eslint-plugin...
//given the amount of extra time it takes, and the the usual lack of any meaningful timely response
//Since I want to continue releasing Excalidraw versions, and not risk getting flagged for failed releases
//I see no other meaningful option
`const deliberateCreateElement = (doc, tagName) => doc.createElement(tagName);\n`;
const BASE_CONFIG = {
input: 'src/core/main.ts',
external: [
'@codemirror/autocomplete',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/search',
'@codemirror/state',
'@codemirror/view',
'@lezer/common',
'@lezer/highlight',
'@lezer/lr',
'obsidian',
'@zsviczian/excalidraw',
'react',
'react-dom'
],
};
const getRollupPlugins = (tsconfig, ...plugins) => [
typescript(tsconfig),
json(),
replace({
preventAssignment: true,
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
}),
replace({ //This is a workaround to silence Obsidian codescanner complain about fs module usage in the plugin code. The plugin does not use fs module, but it is used by some dependencies.
preventAssignment: true,
delimiters: ['', ''],
values: {
"require('fs')": "null",
'require("fs")': "null"
}
}),
commonjs(),
nodeResolve({ browser: true, preferBuiltins: false }),
].concat(plugins);
const BUILD_CONFIG = {
...BASE_CONFIG,
output: {
dir: DIST_FOLDER,
entryFileNames: 'main.js',
format: 'cjs',
exports: 'default',
inlineDynamicImports: true, // Add this line only
},
plugins: getRollupPlugins(
{
tsconfig: isProd ? "tsconfig.json" : "tsconfig.dev.json",
sourceMap: !isProd,
},
...(isProd ? [
terser({
toplevel: false,
compress: { passes: 2 },
format: {
comments: false, // Remove all comments
},
}),
postprocess([
[
/(var[^;]*?),\s*React\s*=\s*require\(["']react["']\)([^;]*;)/,
(_, g1, g2) => `${g1}${g2}${packageString}`
],
]),
/*visualizer({
filename: 'bundle-analysis.html',
open: true, // Automatically opens in your browser when the build finishes
gzipSize: true,
brotliSize: true,
}),*/
] : [
postprocess([[/var React = require\('react'\);/, packageString]]),
]),
copy({
targets: [{ src: 'manifest.json', dest: DIST_FOLDER }],
verbose: true,
}),
),
};
const LIB_CONFIG = {
...BASE_CONFIG,
input: "src/core/index.ts",
output: {
dir: "lib",
sourcemap: false,
format: "cjs",
name: "Excalidraw (Library)",
},
plugins: getRollupPlugins(
{ tsconfig: "tsconfig-lib.json" },
copy({ targets: [{ src: "src/*.d.ts", dest: "lib/typings" }] })
),
};
let config = [];
if (process.env.NODE_ENV === "lib") {
config.push(LIB_CONFIG);
} else {
config.push(BUILD_CONFIG);
}
export default config;