vite-plugin-google-apps-script
Version:
Vite plugin for HtmlService on GoogleAppsScript via @google/clasp
164 lines (155 loc) • 4.99 kB
JavaScript
;
const generator = require('@babel/generator');
const parser = require('@babel/parser');
const _traverse = require('@babel/traverse');
const vite = require('vite');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const _traverse__default = /*#__PURE__*/_interopDefaultCompat(_traverse);
function enforceTerser({ useTerserMinify } = { useTerserMinify: true }) {
return {
name: "vite:enforce-terser",
apply: "build",
config(config) {
if (config.build == null) config.build = {};
if (config.build.minify === "esbuild") {
this.warn(
`[plugin vite:enforce-terser] The plugin will override the "esbuild" minify option to use "terser" or disable minification.`
);
}
config.build.minify = useTerserMinify ? "terser" : false;
}
};
}
const presetReplaceRules = [
// for jsDoc comments
{
from: /\n*\/\*\*[\n\s\S]*?\*\/\n*/g,
to: ""
},
// for scriptlet of apps script
{
from: /"(<\?!{0,1}={0,1}.+?\?>)"/g,
to: "'$1'"
}
];
const defaultOptions = {
useDefault: true,
replaceRules: []
};
const pluginName = "vite:replace-particular-expression";
const replaceParticularExpression = (options) => {
const mergedOptions = { ...defaultOptions, ...options };
return {
name: pluginName,
apply: "build",
enforce: "post",
generateBundle(_outputOptions, outputBundle) {
const chunkNames = Object.keys(outputBundle);
chunkNames.forEach((chunkName) => {
const chunk = outputBundle[chunkName];
const configs = [...mergedOptions.replaceRules];
mergedOptions.useDefault !== false && configs.push(...presetReplaceRules);
configs.forEach(({ from, to, replacer }) => {
const isMatch = typeof from === "string" ? chunk.code.indexOf(from) !== -1 : from.test(chunk.code);
if (isMatch) {
if (replacer != null)
chunk.code = chunk.code.replace(from, replacer);
else if (to != null) chunk.code = chunk.code.replace(from, to);
}
});
outputBundle[chunkName] = chunk;
});
}
};
};
const traverse = _traverse__default.default;
const DEFAULT_URL_REGEX = /\b(?:https?:\/\/|ftp:\/\/|blob:|data:[^'")\s]+|\/\/)[\w/:%#$&?()~.=+\-{}]+/gi;
function sanitizeString(input, pattern) {
if (!pattern) return input.replace(DEFAULT_URL_REGEX, "");
if (pattern instanceof RegExp) return input.replace(pattern, "");
return pattern(input);
}
function toTemplateRaw(cooked) {
return cooked.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
}
function stripUrlsInTemplates(options = {}) {
const {
includes = [/\.([cm]?js|[cm]?ts|jsx|tsx)$/],
excludes,
urlPattern,
parserPlugins = ["jsx", "typescript"]
} = options;
const filter = vite.createFilter(includes, excludes);
return {
name: "vite:strip-urls-in-templates",
apply: "build",
transform(code, id, _options) {
if (!filter(id)) return null;
let ast;
try {
ast = parser.parse(code, {
sourceType: "module",
plugins: parserPlugins,
allowReturnOutsideFunction: true,
allowAwaitOutsideFunction: true
});
} catch {
return null;
}
let templateDepth = 0;
let mutated = false;
const enterTemplate = () => {
templateDepth += 1;
};
const exitTemplate = () => {
templateDepth -= 1;
};
traverse(ast, {
TemplateLiteral: {
enter(path) {
enterTemplate();
for (const elem of path.node.quasis) {
const cooked = elem.value.cooked ?? elem.value.raw;
const sanitized = sanitizeString(String(cooked), urlPattern);
if (sanitized !== cooked) {
mutated = true;
elem.value.cooked = sanitized;
elem.value.raw = toTemplateRaw(sanitized);
}
}
},
exit() {
exitTemplate();
}
},
StringLiteral(path) {
if (templateDepth <= 0) return;
const original = path.node.value;
const sanitized = sanitizeString(original, urlPattern);
if (sanitized !== original) {
mutated = true;
path.node.value = sanitized;
if (path.node.extra) {
path.node.extra.raw = JSON.stringify(sanitized);
path.node.extra.rawValue = sanitized;
}
}
}
});
if (!mutated) return null;
const out = generator.generate(ast, { sourceMaps: true, sourceFileName: id }, code);
return {
code: out.code,
map: out.map || null
};
}
};
}
const gas = ({ minify, url, replace } = {}) => {
return [
enforceTerser(minify),
stripUrlsInTemplates(url),
replaceParticularExpression(replace)
];
};
exports.gas = gas;