-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow reload after changes in imported files (#124)
This changes do several things: 1. Fix the cache clearing after modifications in imported files. The fs-provider uses the file path. The cache uses the URL. I have added some logic to generate the URL from the path. This URL can then be used to clear the cache. 2. Implement logic for clearing the cache in indirectly imported files. The new Map requireCache.knownDependencies contains a list of all files that are imported for a specific file. This information is used to recursively clear the caches of other modules when one of its dependencies has changed. 3. Add more JSDoc Type annotations 4. Some minor syntax cleanups (let => const, || => ??, self=>globalThis, ...) This updates for changes in the main file are still broken due to a bug in a prior commit. --------- Co-authored-by: Davor Hrg <[email protected]>
- Loading branch information
Showing
5 changed files
with
83 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,21 +20,37 @@ export { resolveUrl } from './resolveUrl' | |
// we need eval to do the same without prefix | ||
// https://esbuild.github.io/content-types/#direct-eval | ||
// to be nice to bundlers we need indirect eval | ||
// also self is not available in nodejs | ||
export const runModule = (typeof self === 'undefined' ? eval : self.eval)( | ||
'(require, exports, module, source)=>eval(source)', | ||
) | ||
export const runModule = globalThis.eval('(require, exports, module, source)=>eval(source)') | ||
|
||
/** | ||
* @typedef SourceWithUrl | ||
* @prop {string} url | ||
* @prop {string} script | ||
*/ | ||
|
||
/** | ||
* | ||
* @param {SourceWithUrl | string} urlOrSource | ||
* @param {*} transform | ||
* @param {(path:string,options?:{base:string,output:string})=>string} readFile | ||
* @param {string} base | ||
* @param {string} root | ||
* @param {*} importData | ||
* @param {*} moduleBase | ||
* @returns | ||
*/ | ||
export const require = (urlOrSource, transform, readFile, base, root, importData = null, moduleBase = MODULE_BASE) => { | ||
/** @type {string | undefined} */ | ||
let source | ||
/** @type {string} */ | ||
let url | ||
let isRelativeFile | ||
let cache | ||
let cacheUrl | ||
let bundleAlias | ||
if(typeof urlOrSource === 'string'){ | ||
if (typeof urlOrSource === 'string') {//Only the URL is given | ||
url = urlOrSource | ||
}else{ | ||
} else { //URL and source are given (this is the main file) | ||
source = urlOrSource.script | ||
url = urlOrSource.url | ||
isRelativeFile = true | ||
|
@@ -44,36 +60,40 @@ export const require = (urlOrSource, transform, readFile, base, root, importData | |
|
||
if (source === undefined) { | ||
bundleAlias = requireCache.bundleAlias[url] | ||
const aliasedUrl = bundleAlias || requireCache.alias[url] || url | ||
const aliasedUrl = bundleAlias ?? requireCache.alias[url] ?? url | ||
|
||
const resolved = resolveUrl(aliasedUrl, base, root, moduleBase) | ||
const resolvedStr = resolved.url.toString() | ||
const arr = resolvedStr.split('/') | ||
const urlComponents = resolvedStr.split('/') | ||
// no file ext is usually module from CDN | ||
const isJs = !arr[arr.length-1].includes('.') || resolvedStr.endsWith('.ts') || resolvedStr.endsWith('.js') | ||
if(!isJs && importData){ | ||
const isJs = !urlComponents[urlComponents.length - 1].includes('.') || resolvedStr.endsWith('.ts') || resolvedStr.endsWith('.js') | ||
if (!isJs && importData) { | ||
const info = extractPathInfo(resolvedStr) | ||
let content = readFile(resolvedStr,{output: importData.isBinaryExt(info.ext)}) | ||
const content = readFile(resolvedStr, { output: importData.isBinaryExt(info.ext) }) | ||
return importData.deserialize(info, content) | ||
} | ||
|
||
isRelativeFile = resolved.isRelativeFile | ||
resolvedUrl = resolved.url | ||
cacheUrl = resolved.url | ||
requireCache.knownDependencies.get(base)?.add(cacheUrl)//Mark this module as a dependency of the base module | ||
|
||
cache = requireCache[isRelativeFile ? 'local':'module'] | ||
cache = requireCache[isRelativeFile ? 'local' : 'module'] | ||
exports = cache[cacheUrl] // get from cache | ||
if (!exports) { | ||
// not cached | ||
|
||
//Clear the known dependencies of the old version this module | ||
requireCache.knownDependencies.set(cacheUrl, new Set()) | ||
try { | ||
source = readFile(resolvedUrl) | ||
if (resolvedUrl.includes('jsdelivr.net')) { | ||
// jsdelivr will read package.json and tell us what the main file is | ||
const srch = ' * Original file: ' | ||
let idx = source.indexOf(srch) | ||
if (idx != -1) { | ||
const idx2 = source.indexOf('\n', idx+srch.length+1) | ||
const realFile = new URL(source.substring(idx+srch.length, idx2), resolvedUrl).toString() | ||
const idx2 = source.indexOf('\n', idx + srch.length + 1) | ||
const realFile = new URL(source.substring(idx + srch.length, idx2), resolvedUrl).toString() | ||
resolvedUrl = base = realFile | ||
} | ||
} | ||
|
@@ -93,23 +113,23 @@ export const require = (urlOrSource, transform, readFile, base, root, importData | |
} | ||
} | ||
if (source !== undefined) { | ||
let extension = getExtension(resolvedUrl) | ||
const extension = getExtension(resolvedUrl) | ||
// https://cdn.jsdelivr.net/npm/@jscad/[email protected]/index.js uses require to read package.json | ||
if (extension === 'json') { | ||
exports = JSON.parse(source) | ||
} else { | ||
// do not transform bundles that are already cjs ( requireCache.bundleAlias.*) | ||
if (transform && !bundleAlias) source = transform(source, resolvedUrl).code | ||
// construct require function relative to resolvedUrl | ||
let requireFunc = newUrl => require(newUrl, transform, readFile, resolvedUrl, root, importData, moduleBase) | ||
const requireFunc = newUrl => require(newUrl, transform, readFile, resolvedUrl, root, importData, moduleBase) | ||
const module = requireModule(url, resolvedUrl, source, requireFunc) | ||
module.local = isRelativeFile | ||
exports = module.exports | ||
// import jscad from "@jscad/modeling"; | ||
// will be effectively transformed to | ||
// const jscad = require('@jscad/modeling').default | ||
// we need to plug-in default if missing | ||
if(!('default' in exports)) exports.default = exports | ||
if (!('default' in exports)) exports.default = exports | ||
} | ||
} | ||
|
||
|
@@ -132,14 +152,38 @@ const requireModule = (id, url, source, _require) => { | |
} | ||
} | ||
|
||
/** | ||
* @typedef ClearFileCacheOptions | ||
* @prop {Array<String>} files | ||
* @prop {string} root | ||
*/ | ||
|
||
/** | ||
* Clear file cache for specific files. Used when a file has changed. | ||
* @param {ClearFileCacheOptions} obj | ||
*/ | ||
export const clearFileCache = async ({files}) => { | ||
export const clearFileCache = ({ files, root }) => { | ||
const cache = requireCache.local | ||
files.forEach(f=>{ | ||
delete cache[f] | ||
}) | ||
|
||
/** | ||
* @param {string} url | ||
*/ | ||
const clearDependencies = (url) => { | ||
delete cache[url] | ||
const dependents = [...requireCache.knownDependencies.entries()].filter(([_, value]) => value.has(url)) | ||
for (const [dependency, _] of dependents) { | ||
clearDependencies(dependency) | ||
} | ||
} | ||
|
||
for (const file of files) { | ||
delete cache[file] | ||
if (root !== undefined) { | ||
const path = file.startsWith("/") ? `.${file}` : file | ||
const url = new URL(path, root) | ||
clearDependencies(url.toString()) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -150,9 +194,20 @@ export const jscadClearTempCache = () => { | |
requireCache.alias = {} | ||
} | ||
|
||
|
||
/** | ||
* @type {{ | ||
* local:Object.<string,Object> | ||
* alias:Object.<string,string> | ||
* module:Object.<string,Object> | ||
* bundleAlias:Object.<string,string> | ||
* knownDependencies:Map.<string,Set<string>> | ||
* }} | ||
*/ | ||
export const requireCache = { | ||
local: {}, | ||
alias: {}, | ||
module: {}, | ||
bundleAlias: {}, | ||
knownDependencies: new Map(), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters