Skip to content

Instantly share code, notes, and snippets.

@aleclarson
Last active May 11, 2022 06:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aleclarson/40a2f10f0f3bedd4ae5eee4e444e033f to your computer and use it in GitHub Desktop.
Save aleclarson/40a2f10f0f3bedd4ae5eee4e444e033f to your computer and use it in GitHub Desktop.
rollup-plugin-preserve-jsx

rollup-plugin-preserve-jsx

Preserve JSX with proper tree-shaking.

  • acorn-jsx is injected for you.
  • @babel/plugin-syntax-jsx is needed if you're using Babel.

 

TODO

  • generate sourcemaps with magic-string
{
"name": "rollup-plugin-preserve-jsx",
"dependencies": {
"acorn-jsx": "5.3.1",
"estree-walker": "2.0.1",
"magic-string": "0.25.7"
}
}
// Taken from: https://github.com/rollup/rollup/issues/2822#issuecomment-486676465
import MagicString from 'magic-string';
import { walk } from 'estree-walker';
import jsx from 'acorn-jsx';
let nextId = 0;
function getJsxName(node) {
if (node.type === 'JSXMemberExpression') {
return `${getJsxName(node.object)}.${getJsxName(node.property)}`;
}
return node.name;
}
/**
* @returns {import('rollup').Plugin}
*/
export default () => ({
options(inputOptions) {
const acornPlugins = inputOptions.acornInjectPlugins || (inputOptions.acornInjectPlugins = []);
acornPlugins.push(jsx());
},
transform(code) {
const magicString = new MagicString(code);
const idsByName = new Map();
const ast = this.parse(code);
walk(ast, {
enter(node) {
if (node.type === 'JSXOpeningElement' || node.type === 'JSXClosingElement') {
const name = getJsxName(node.name);
const tagId = idsByName.get(name) || `JSX_PLUGIN_ID_${nextId++}`;
// overwrite all JSX tags with artificial tag ids so that we can find them again later
magicString.overwrite(node.name.start, node.name.end, tagId);
idsByName.set(name, tagId);
}
// do not treat the children as separate identifiers
else if (node.type === 'JSXMemberExpression') {
this.skip();
}
},
});
if (idsByName.size > 0) {
const usedNamesAndIds = Array.from(idsByName).map(([name, tagId]) => `/*${tagId}*/${name}`);
magicString.append(`;USED_JSX_NAMES(React,${usedNamesAndIds.join(',')});`);
return magicString.toString();
}
},
renderChunk(code) {
const replacements = new Map();
// this finds all injected artificial usages from the transform hook, removes them
// and collects the new variable names as a side-effect
code = code.replace(/USED_JSX_NAMES\(([^)]*)\);/g, (_, args) => {
// this extracts the artificial tag id from the comment and the possibly renamed variable
// name from the variable via two capture groups
const usedNames = args.split(',').map(arg => arg.match(/^\/\*([^*]*)\*\/(.*)$/));
usedNames.slice(1).forEach(([_, tagId, updatedName]) => {
replacements.set(tagId, updatedName);
});
return '';
});
// this replaces the artificial tag ids in the actual JSX tags
return code.replace(/JSX_PLUGIN_ID_\d+/g, tagId => replacements.get(tagId));
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment