solid-refresh
Advanced tools
Comparing version 0.2.2 to 0.3.0
345
babel.js
@@ -1,114 +0,237 @@ | ||
module.exports = ({ types: t }) => { | ||
return { | ||
name: "Solid Refresh", | ||
visitor: { | ||
Program(path, { opts }) { | ||
const comments = path.hub.file.ast.comments; | ||
for (let i = 0; i < comments.length; i++) { | ||
const comment = comments[i]; | ||
const index = comment.value.indexOf("@refresh"); | ||
if (index > -1) { | ||
if (comment.value.slice(index).includes("skip")) { | ||
path.hub.file.metadata.processedHot = true; | ||
return; | ||
'use strict'; | ||
var t = require('@babel/types'); | ||
var generator = require('@babel/generator'); | ||
var helperModuleImports = require('@babel/helper-module-imports'); | ||
var crypto = require('crypto'); | ||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
function _interopNamespace(e) { | ||
if (e && e.__esModule) return e; | ||
var n = Object.create(null); | ||
if (e) { | ||
Object.keys(e).forEach(function (k) { | ||
if (k !== 'default') { | ||
var d = Object.getOwnPropertyDescriptor(e, k); | ||
Object.defineProperty(n, k, d.get ? d : { | ||
enumerable: true, | ||
get: function () { | ||
return e[k]; | ||
} | ||
}); | ||
} | ||
if (comment.value.slice(index).includes("reload")) { | ||
if (opts.bundler === "vite") opts.bundler = "esm"; | ||
path.hub.file.metadata.processedHot = true; | ||
const pathToHot = | ||
opts.bundler !== "esm" | ||
? t.memberExpression(t.identifier("module"), t.identifier("hot")) | ||
: t.memberExpression( | ||
t.memberExpression(t.identifier("import"), t.identifier("meta")), | ||
t.identifier("hot") | ||
); | ||
path.pushContainer( | ||
"body", | ||
t.ifStatement( | ||
pathToHot, | ||
t.expressionStatement( | ||
t.callExpression(t.memberExpression(pathToHot, t.identifier("decline")), []) | ||
) | ||
) | ||
); | ||
return; | ||
}); | ||
} | ||
n['default'] = e; | ||
return Object.freeze(n); | ||
} | ||
var t__namespace = /*#__PURE__*/_interopNamespace(t); | ||
var generator__default = /*#__PURE__*/_interopDefaultLegacy(generator); | ||
var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto); | ||
function isComponentishName(name) { | ||
return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z'; | ||
} | ||
function getSolidRefreshIdentifier(hooks, path, name) { | ||
const current = hooks.get(name); | ||
if (current) { | ||
return current; | ||
} | ||
const newID = helperModuleImports.addNamed(path, name, 'solid-refresh'); | ||
hooks.set(name, newID); | ||
return newID; | ||
} | ||
function getHotIdentifier(bundler) { | ||
if (bundler === 'esm') { | ||
return t__namespace.memberExpression(t__namespace.memberExpression(t__namespace.identifier('import'), t__namespace.identifier('meta')), t__namespace.identifier('hot')); | ||
} | ||
return t__namespace.memberExpression(t__namespace.identifier("module"), t__namespace.identifier("hot")); | ||
} | ||
function getStatementPath(path) { | ||
if (t__namespace.isStatement(path.node)) { | ||
return path; | ||
} | ||
if (path.parentPath) { | ||
return getStatementPath(path.parentPath); | ||
} | ||
return null; | ||
} | ||
function createHotMap(hooks, path, name) { | ||
const current = hooks.get(name); | ||
if (current) { | ||
return current; | ||
} | ||
const newID = t__namespace.identifier(name); | ||
path.insertBefore(t__namespace.exportNamedDeclaration(t__namespace.variableDeclaration('const', [t__namespace.variableDeclarator(newID, t__namespace.objectExpression([]))]))); | ||
hooks.set(name, newID); | ||
return newID; | ||
} | ||
function createSignature(node) { | ||
const code = generator__default['default'](node); | ||
const result = crypto__default['default'].createHash('sha256').update(code.code).digest('base64'); | ||
return result; | ||
} | ||
function createStandardHot(path, hooks, opts, HotComponent, rename) { | ||
const HotImport = getSolidRefreshIdentifier(hooks, path, opts.bundler || 'standard'); | ||
const pathToHot = getHotIdentifier(opts.bundler); | ||
const statementPath = getStatementPath(path); | ||
if (statementPath) { | ||
statementPath.insertBefore(rename); | ||
} | ||
return t__namespace.callExpression(HotImport, [ | ||
HotComponent, | ||
t__namespace.stringLiteral(HotComponent.name), | ||
t__namespace.stringLiteral(createSignature(rename)), | ||
pathToHot, | ||
]); | ||
} | ||
function createESMHot(path, hooks, opts, HotComponent, rename) { | ||
const HotImport = getSolidRefreshIdentifier(hooks, path, opts.bundler || 'standard'); | ||
const pathToHot = getHotIdentifier(opts.bundler); | ||
const handlerId = path.scope.generateUidIdentifier("handler"); | ||
const componentId = path.scope.generateUidIdentifier("Component"); | ||
const statementPath = getStatementPath(path); | ||
if (statementPath) { | ||
const registrationMap = createHotMap(hooks, statementPath, '$$registrations'); | ||
statementPath.insertBefore(rename); | ||
statementPath.insertBefore(t__namespace.expressionStatement(t__namespace.assignmentExpression('=', t__namespace.memberExpression(registrationMap, HotComponent), t__namespace.objectExpression([ | ||
t__namespace.objectProperty(t__namespace.identifier('component'), HotComponent), | ||
t__namespace.objectProperty(t__namespace.identifier('signature'), t__namespace.stringLiteral(createSignature(rename))), | ||
])))); | ||
statementPath.insertBefore(t__namespace.variableDeclaration("const", [ | ||
t__namespace.variableDeclarator(t__namespace.objectPattern([ | ||
t__namespace.objectProperty(t__namespace.identifier('handler'), handlerId, false, true), | ||
t__namespace.objectProperty(t__namespace.identifier('Component'), componentId, false, true) | ||
]), t__namespace.callExpression(HotImport, [ | ||
HotComponent, | ||
t__namespace.stringLiteral(HotComponent.name), | ||
t__namespace.memberExpression(t__namespace.memberExpression(registrationMap, HotComponent), t__namespace.identifier('signature')), | ||
t__namespace.unaryExpression("!", t__namespace.unaryExpression("!", pathToHot)) | ||
])) | ||
])); | ||
statementPath.insertBefore(t__namespace.ifStatement(pathToHot, t__namespace.expressionStatement(t__namespace.callExpression(t__namespace.memberExpression(pathToHot, t__namespace.identifier("accept")), [handlerId])))); | ||
} | ||
return componentId; | ||
} | ||
function createHot(path, hooks, opts, name, expression) { | ||
if (opts.bundler === "vite") | ||
opts.bundler = "esm"; | ||
const HotComponent = name | ||
? path.scope.generateUidIdentifier(`Hot$$${name.name}`) | ||
: path.scope.generateUidIdentifier('HotComponent'); | ||
const rename = t__namespace.variableDeclaration("const", [ | ||
t__namespace.variableDeclarator(HotComponent, expression), | ||
]); | ||
if (opts.bundler === "esm") { | ||
return createESMHot(path, hooks, opts, HotComponent, rename); | ||
} | ||
return createStandardHot(path, hooks, opts, HotComponent, rename); | ||
} | ||
function solidRefreshPlugin() { | ||
return { | ||
name: 'Solid Refresh', | ||
pre() { | ||
this.hooks = new Map(); | ||
this.processed = { | ||
value: false, | ||
}; | ||
}, | ||
visitor: { | ||
Program(path, { opts, processed }) { | ||
const comments = path.hub.file.ast.comments; | ||
for (let i = 0; i < comments.length; i++) { | ||
const comment = comments[i].value; | ||
if (/^\s*@refresh skip\s*$/.test(comment)) { | ||
processed.value = true; | ||
return; | ||
} | ||
if (/^\s*@refresh reload\s*$/.test(comment)) { | ||
if (opts.bundler === "vite") | ||
opts.bundler = "esm"; | ||
processed.value = true; | ||
const pathToHot = getHotIdentifier(opts.bundler); | ||
path.pushContainer("body", t__namespace.ifStatement(pathToHot, t__namespace.expressionStatement(t__namespace.callExpression(t__namespace.memberExpression(pathToHot, t__namespace.identifier("decline")), [])))); | ||
return; | ||
} | ||
} | ||
}, | ||
ExportNamedDeclaration(path, { opts, hooks, processed }) { | ||
if (processed.value) { | ||
return; | ||
} | ||
const decl = path.node.declaration; | ||
// Check if declaration is FunctionDeclaration | ||
if (t__namespace.isFunctionDeclaration(decl) && !(decl.generator || decl.async)) { | ||
// Check if the declaration has an identifier, and then check | ||
// if the name is component-ish | ||
if (decl.id && isComponentishName(decl.id.name)) { | ||
path.node.declaration = t__namespace.variableDeclaration('const', [ | ||
t__namespace.variableDeclarator(decl.id, createHot(path, hooks, opts, decl.id, t__namespace.functionExpression(decl.id, decl.params, decl.body))) | ||
]); | ||
} | ||
} | ||
}, | ||
VariableDeclarator(path, { opts, hooks, processed }) { | ||
var _a, _b; | ||
if (processed.value) { | ||
return; | ||
} | ||
const grandParentNode = (_b = (_a = path.parentPath) === null || _a === void 0 ? void 0 : _a.parentPath) === null || _b === void 0 ? void 0 : _b.node; | ||
// Check if the parent of the VariableDeclaration | ||
// is either a Program or an ExportNamedDeclaration | ||
if (t__namespace.isProgram(grandParentNode) | ||
|| t__namespace.isExportNamedDeclaration(grandParentNode)) { | ||
const identifier = path.node.id; | ||
const init = path.node.init; | ||
if (t__namespace.isIdentifier(identifier) | ||
&& isComponentishName(identifier.name) | ||
&& ( | ||
// Check for valid FunctionExpression | ||
(t__namespace.isFunctionExpression(init) && !(init.async || init.generator)) | ||
// Check for valid ArrowFunctionExpression | ||
|| (t__namespace.isArrowFunctionExpression(init) && !(init.async || init.generator)))) { | ||
path.node.init = createHot(path, hooks, opts, identifier, init); | ||
} | ||
} | ||
}, | ||
FunctionDeclaration(path, { opts, hooks, processed }) { | ||
if (processed.value) { | ||
return; | ||
} | ||
if (!(t__namespace.isProgram(path.parentPath.node) | ||
|| t__namespace.isExportDefaultDeclaration(path.parentPath.node))) { | ||
return; | ||
} | ||
const decl = path.node; | ||
// Check if declaration is FunctionDeclaration | ||
if (!(decl.generator || decl.async)) { | ||
// Check if the declaration has an identifier, and then check | ||
// if the name is component-ish | ||
if (decl.id && isComponentishName(decl.id.name)) { | ||
const replacement = createHot(path, hooks, opts, decl.id, t__namespace.functionExpression(decl.id, decl.params, decl.body)); | ||
if (t__namespace.isExportDefaultDeclaration(path.parentPath.node)) { | ||
path.replaceWith(replacement); | ||
} | ||
else { | ||
path.replaceWith(t__namespace.variableDeclaration('var', [ | ||
t__namespace.variableDeclarator(decl.id, replacement), | ||
])); | ||
} | ||
} | ||
else if (!decl.id | ||
&& decl.params.length === 1 | ||
&& t__namespace.isIdentifier(decl.params[0]) | ||
&& decl.params[0].name === 'props' | ||
&& t__namespace.isExportDefaultDeclaration(path.parentPath.node)) { | ||
const replacement = createHot(path, hooks, opts, undefined, t__namespace.functionExpression(null, decl.params, decl.body)); | ||
path.replaceWith(replacement); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
ExportDefaultDeclaration(path, { opts }) { | ||
if (path.hub.file.metadata.processedHot) return; | ||
if ( | ||
path.hub.file.opts.parserOpts.sourceFileName && | ||
!path.hub.file.opts.parserOpts.sourceFileName.endsWith(".jsx") && | ||
!path.hub.file.opts.parserOpts.sourceFileName.endsWith(".tsx") | ||
) | ||
return; | ||
if (opts.bundler === "vite") opts.bundler = "esm"; | ||
path.hub.file.metadata.processedHot = true; | ||
const decl = path.node.declaration; | ||
const HotComponent = t.identifier("$HotComponent"); | ||
const HotImport = t.identifier("_$hot"); | ||
const pathToHot = | ||
opts.bundler !== "esm" | ||
? t.memberExpression(t.identifier("module"), t.identifier("hot")) | ||
: t.memberExpression( | ||
t.memberExpression(t.identifier("import"), t.identifier("meta")), | ||
t.identifier("hot") | ||
); | ||
const rename = t.variableDeclaration("const", [ | ||
t.variableDeclarator( | ||
HotComponent, | ||
t.isFunctionDeclaration(decl) | ||
? t.functionExpression(decl.id, decl.params, decl.body) | ||
: decl | ||
) | ||
]); | ||
let replacement; | ||
if (opts.bundler === "esm") { | ||
const handlerId = t.identifier("_$handler"); | ||
const componentId = t.identifier("_$Component"); | ||
replacement = [ | ||
t.importDeclaration( | ||
[t.importSpecifier(HotImport, t.identifier(opts.bundler || "standard"))], | ||
t.stringLiteral("solid-refresh") | ||
), | ||
t.exportNamedDeclaration(rename), | ||
t.variableDeclaration("const", [ | ||
t.variableDeclarator( | ||
t.objectPattern([ | ||
t.objectProperty(handlerId, handlerId, false, true), | ||
t.objectProperty(componentId, componentId, false, true) | ||
]), | ||
t.callExpression(HotImport, [ | ||
HotComponent, | ||
t.unaryExpression("!", t.unaryExpression("!", pathToHot)) | ||
]) | ||
) | ||
]), | ||
t.ifStatement( | ||
pathToHot, | ||
t.expressionStatement( | ||
t.callExpression(t.memberExpression(pathToHot, t.identifier("accept")), [handlerId]) | ||
) | ||
), | ||
t.exportDefaultDeclaration(componentId) | ||
]; | ||
} else { | ||
replacement = [ | ||
t.importDeclaration( | ||
[t.importSpecifier(HotImport, t.identifier(opts.bundler || "standard"))], | ||
t.stringLiteral("solid-refresh") | ||
), | ||
rename, | ||
t.exportDefaultDeclaration(t.callExpression(HotImport, [HotComponent, pathToHot])) | ||
]; | ||
} | ||
}, | ||
}; | ||
} | ||
path | ||
.replaceWithMultiple(replacement) | ||
.forEach(declaration => path.scope.registerDeclaration(declaration)); | ||
} | ||
} | ||
}; | ||
}; | ||
module.exports = solidRefreshPlugin; |
@@ -7,38 +7,55 @@ 'use strict'; | ||
function hot$1(Comp, hot) { | ||
if (hot) { | ||
const [comp, setComp] = solidJs.createSignal(Comp); | ||
const prev = hot.data; | ||
if (prev && prev.setComp) { | ||
prev.setComp(() => Comp); | ||
function hot$1(Comp, id, initialSignature, hot) { | ||
if (hot) { | ||
const [comp, setComp] = solidJs.createSignal(Comp); | ||
const [sign, setSign] = solidJs.createSignal(initialSignature); | ||
const prev = hot.data; | ||
if (prev && prev[id] && prev[id].sign() !== initialSignature) { | ||
prev[id].setSign(() => initialSignature); | ||
prev[id].setComp(() => Comp); | ||
} | ||
hot.dispose(data => { | ||
data[id] = prev ? prev[id] : { | ||
setComp, | ||
sign, | ||
setSign, | ||
}; | ||
}); | ||
hot.accept(); | ||
let c; | ||
return new Proxy((props) => solidJs.createMemo(() => (c = comp()) && solidJs.untrack(() => c(props))), { | ||
get(_, property) { | ||
return comp()[property]; | ||
} | ||
}); | ||
} | ||
hot.dispose(data => (data.setComp = prev ? prev.setComp : setComp)); | ||
hot.accept(); | ||
let c; | ||
return new Proxy(props => solidJs.createMemo(() => (c = comp()) && solidJs.untrack(() => c(props))), { | ||
get(_, property) { | ||
return comp()[property]; | ||
} | ||
}); | ||
} | ||
return Comp; | ||
return Comp; | ||
} | ||
function hot(Comp, isHot) { | ||
let _$Component = Comp; | ||
function _$handler(newModule) { | ||
newModule.$HotComponent.setComp = Comp.setComp; | ||
Comp.setComp(() => newModule.$HotComponent); | ||
} | ||
if (isHot) { | ||
const [comp, setComp] = solidJs.createSignal(Comp); | ||
Comp.setComp = setComp; | ||
let c; | ||
_$Component = new Proxy(props => solidJs.createMemo(() => (c = comp()) && solidJs.untrack(() => c(props))), { | ||
get(_, property) { | ||
return comp()[property]; | ||
} | ||
}); | ||
} | ||
return { _$Component, _$handler }; | ||
function hot(Comp, id, initialSignature, isHot) { | ||
let Component = Comp; | ||
function handler(newModule) { | ||
const registration = newModule.$$registrations[id]; | ||
registration.component.setComp = Comp.setComp; | ||
registration.component.setSign = Comp.setSign; | ||
registration.component.sign = Comp.sign; | ||
if (registration.signature !== Comp.sign()) { | ||
Comp.setSign(() => registration.signature); | ||
Comp.setComp(() => registration.component); | ||
} | ||
} | ||
if (isHot) { | ||
const [comp, setComp] = solidJs.createSignal(Comp); | ||
const [signature, setSignature] = solidJs.createSignal(initialSignature); | ||
Comp.setComp = setComp; | ||
Comp.setSign = setSignature; | ||
Comp.sign = signature; | ||
let c; | ||
Component = new Proxy((props) => solidJs.createMemo(() => (c = comp()) && solidJs.untrack(() => c(props))), { | ||
get(_, property) { | ||
return comp()[property]; | ||
} | ||
}); | ||
} | ||
return { Component, handler }; | ||
} | ||
@@ -45,0 +62,0 @@ |
@@ -6,3 +6,3 @@ { | ||
"license": "MIT", | ||
"version": "0.2.2", | ||
"version": "0.3.0", | ||
"homepage": "https://github.com/solidjs/solid-refresh#readme", | ||
@@ -19,2 +19,8 @@ "repository": { | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"contributors": [ | ||
"Alexis Munsayac" | ||
], | ||
"sideEffects": false, | ||
@@ -27,8 +33,16 @@ "scripts": { | ||
"@rollup/plugin-node-resolve": "13.0.0", | ||
"@rollup/plugin-typescript": "^8.3.0", | ||
"rollup": "^2.52.1", | ||
"solid-js": "^1.0.0" | ||
"solid-js": "^1.0.0", | ||
"@types/babel__core": "^7.1.16", | ||
"@babel/core": "^7.16.0" | ||
}, | ||
"peerDependencies": { | ||
"solid-js": "^1.0.0" | ||
}, | ||
"dependencies": { | ||
"@babel/types": "^7.16.0", | ||
"@babel/helper-module-imports": "^7.16.0", | ||
"@babel/generator": "^7.16.0" | ||
} | ||
} |
@@ -11,16 +11,40 @@ # Solid Refresh | ||
* Snowpack (with option `bundler: "esm"`, need to confirm) | ||
## How it works | ||
The babel plugin will transform files with `.jsx` or `.tsx` extensions with default exports (assuming they are component files) to wrap the default export with `createMemo` call so component can be swapped at will. | ||
The babel plugin will transform components with matching Pascal-cased names (indicating that they are components). This detection is supported in variable declarations, function declarations and named exports: | ||
Today we don't preserve state below the change. | ||
```jsx | ||
// This works | ||
function Foo() { | ||
return <h1>Hello Foo</h1>; | ||
} | ||
// This also works | ||
const Bar = () => <h1>Hello Bar</h1>; | ||
``` | ||
Anonymous functions with `props` as the only parameter are also supported. | ||
```js | ||
// This also works | ||
export default function (props) { | ||
return <h1>Hello Anonymous!</h1>; | ||
} | ||
``` | ||
The components are wrapped and memoized. When the module receives an update, it tries to detect if the component's content has changed between updates, prompting a remount for the changed component (which allows the ancestor components to retain their lifecycle.). | ||
## Pragma | ||
On a per file basis, use comments at top of file to opt out(change moves up to parent): | ||
```js | ||
/* @refresh skip */ | ||
``` | ||
Or force reload: | ||
```js | ||
/* @refresh reload */ | ||
``` | ||
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
18764
344
50
4
6
1
+ Added@babel/generator@^7.16.0
+ Added@babel/types@^7.16.0
+ Added@babel/code-frame@7.26.2(transitive)
+ Added@babel/generator@7.26.9(transitive)
+ Added@babel/helper-module-imports@7.25.9(transitive)
+ Added@babel/helper-string-parser@7.25.9(transitive)
+ Added@babel/helper-validator-identifier@7.25.9(transitive)
+ Added@babel/parser@7.26.9(transitive)
+ Added@babel/template@7.26.9(transitive)
+ Added@babel/traverse@7.26.9(transitive)
+ Added@babel/types@7.26.9(transitive)
+ Added@jridgewell/gen-mapping@0.3.8(transitive)
+ Added@jridgewell/resolve-uri@3.1.2(transitive)
+ Added@jridgewell/set-array@1.2.1(transitive)
+ Added@jridgewell/sourcemap-codec@1.5.0(transitive)
+ Added@jridgewell/trace-mapping@0.3.25(transitive)
+ Addeddebug@4.4.0(transitive)
+ Addedglobals@11.12.0(transitive)
+ Addedjs-tokens@4.0.0(transitive)
+ Addedjsesc@3.1.0(transitive)
+ Addedms@2.1.3(transitive)
+ Addedpicocolors@1.1.1(transitive)