UNPKG

@lavamoat/webpack

Version:

LavaMoat Webpack plugin for running dependencies in Compartments without eval

120 lines (112 loc) 3.69 kB
const { readFileSync } = require('node:fs') const { parse } = require('@babel/parser') /** * Webpack adds a multiline comment in front of every line of the runtime code. * They break actually-multiline comments as a result. This is a necessary step * to prevent a runtime error in development mode. * * @param {string} source * @returns {string} */ function removeMultilineComments(source) { try { const ast = parse(source, { sourceType: 'script' }) return (ast.comments || []) .filter((c) => c.type === 'CommentBlock') .reverse() .reduce( (src, { start, end }) => src.slice(0, start) + src.slice(end), source ) } catch (e) { throw Error( `Failed to remove multiline comments. \n ${e} \n___________\n ${source}\n___________` ) } } // Runtime modules are not being built nor wrapped by webpack, so I rolled my own tiny concatenator. // It's using a shared namespace technique instead of scoping `exports` variables // to avoid confusing anyone into believing it's actually CJS. // Criticism will only be accepted in a form of working PR with less total lines and less magic. /** * Loads the source code for a given module specifier. * * @param {string} specifier - The module specifier to load. * @returns {string} The loaded source code. */ function loadSource(specifier) { return readFileSync(require.resolve(specifier), 'utf-8') } /** * Wraps the source code for a given module in a function that simulates the * CommonJS module system. * * @param {string} sourceString - The source code to wrap. * @param {string} [name] - The name of the module. * @returns {string} The wrapped source code. */ function shimSource(sourceString, name = 'unknown') { return `;(()=>{ const module = {exports: {}}; const exports = module.exports; ${removeMultilineComments(sourceString)} ; LAVAMOAT['${name}'] = module.exports; })();` } /** * Prepares the source code for a given module specifier. * * @param {string} specifier - The module specifier to prepare. * @returns {string} The prepared source code. */ function prepareSource(specifier) { return removeMultilineComments(loadSource(specifier)) } /** * @param {string} KEY * @param {readonly RuntimeFragment[]} runtimeModules * @returns {string} */ const assembleRuntime = (KEY, runtimeModules) => { let assembly = 'const LAVAMOAT = Object.create(null);' runtimeModules.map(({ file, data, name, json, rawSource, shimRequire }) => { let sourceString if (file) { sourceString = readFileSync(file, 'utf-8') } if (rawSource) { sourceString = rawSource } if (data) { sourceString = JSON.stringify(data) } if (json) { sourceString = `LAVAMOAT['${name}'] = (${sourceString});` } else { if (shimRequire) { sourceString = loadSource(shimRequire) } if ((file || rawSource || shimRequire) && sourceString) { sourceString = shimSource(sourceString, name) } } if (sourceString) { assembly += `\n;/*${name}*/;\n${sourceString}` } }) assembly += `; __webpack_require__.${KEY} = LAVAMOAT.defaultExport; (typeof harden !== 'undefined') && harden(__webpack_require__.${KEY});` // The harden line is likely unnecessary as the handler is being frozen anyway return assembly } /** * @typedef RuntimeFragment * @property {string} [file] * @property {unknown} [data] * @property {string} [name] * @property {boolean} [json] * @property {string} [rawSource] * @property {string} [shimRequire] */ module.exports = { assembleRuntime, prepareSource, removeMultilineComments }