Skip to content

Commit

Permalink
server/embed: add npm polyfills
Browse files Browse the repository at this point in the history
  • Loading branch information
ije committed Jul 11, 2024
1 parent c7a1307 commit cf60a09
Show file tree
Hide file tree
Showing 44 changed files with 116 additions and 68 deletions.
8 changes: 0 additions & 8 deletions scripts/update-npm-polyfills.sh

This file was deleted.

5 changes: 3 additions & 2 deletions server/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,10 +448,11 @@ func (ctx *BuildContext) buildModule() (result BuildResult, err error) {
}
}

// use polyfilled 'fsevents' module for browser
// use the polyfilled 'fsevents' module for browser
if specifier == "fsevents" && ctx.isBrowserTarget() {
data := npmPolyfills[specifier]
return api.OnResolveResult{
Path: "npm_fsevents.js",
Path: fmt.Sprintf("data:text/javascript;base64,%s", base64.StdEncoding.EncodeToString(data)),
External: true,
}, nil
}
Expand Down
2 changes: 1 addition & 1 deletion server/build_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv
return
}
if specifier == "node-fetch" && ctx.target != "node" {
resolvedPath = "/npm_node-fetch.js"
resolvedPath = "/node/fetch.js"
return
}

Expand Down
49 changes: 23 additions & 26 deletions server/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ var (
)

type EmbedFS interface {
ReadDir(name string) ([]os.DirEntry, error)
ReadFile(name string) ([]byte, error)
}

type MockEmbedFS struct {
cwd string
}

func (fs MockEmbedFS) ReadDir(name string) ([]os.DirEntry, error) {
return os.ReadDir(path.Join(fs.cwd, name))
}

func (fs MockEmbedFS) ReadFile(name string) ([]byte, error) {
return os.ReadFile(path.Join(fs.cwd, name))
}
Expand Down Expand Up @@ -57,46 +62,38 @@ func loadNodeLibs(fs EmbedFS) (err error) {
}
}
// override some libs
node_async_hooks_js, err := fs.ReadFile("server/embed/polyfills/node_async_hooks.js")
entries, err := fs.ReadDir("server/embed/polyfills")
if err != nil {
return
}
nodeLibs["node/async_hooks.js"] = string(node_async_hooks_js)
// extra libs
node_filename_resolver_js, err := fs.ReadFile("server/embed/polyfills/node_filename_resolver.js")
if err != nil {
return
for _, entry := range entries {
name := entry.Name()
if !entry.IsDir() && strings.HasPrefix(name, "node_") && strings.HasSuffix(name, ".js") {
data, err := fs.ReadFile("server/embed/polyfills/" + name)
if err != nil {
return err
}
nodeLibs["node/"+name[5:]] = string(data)
}
}
nodeLibs["node/filename_resolver.js"] = string(node_filename_resolver_js)
return nil
}

func loadNpmPolyfills(fs EmbedFS) (err error) {
data, err := fs.ReadFile("server/embed/npm-polyfills.tar.gz")
entries, err := fs.ReadDir("server/embed/polyfills/npm")
if err != nil {
return
}
gr, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return
}
tr := tar.NewReader(gr)
for {
h, err := tr.Next()
if err != nil {
break
}
if h.Typeflag == tar.TypeReg {
data, err := io.ReadAll(tr)
for _, entry := range entries {
name := entry.Name()
if !entry.IsDir() && strings.HasSuffix(name, ".mjs") {
data, err := fs.ReadFile("server/embed/polyfills/npm/" + name)
if err != nil {
return err
}
if strings.HasSuffix(h.Name, ".mjs") {
name := strings.TrimSuffix(path.Base(h.Name), ".mjs")
data = bytes.ReplaceAll(data, []byte{';', '\n'}, []byte{';'})
data = bytes.TrimSuffix(data, []byte{';'})
npmPolyfills[name] = data
}
data = bytes.ReplaceAll(data, []byte{';', '\n'}, []byte{';'})
data = bytes.TrimSuffix(data, []byte{';'})
npmPolyfills[strings.TrimSuffix(name, ".mjs")] = data
}
}
return nil
Expand Down
Binary file removed server/embed/npm-polyfills.tar.gz
Binary file not shown.
File renamed without changes.
37 changes: 37 additions & 0 deletions server/embed/polyfills/npm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# esm-npm-polyfills

Using _native_ APIs instead of importing from NPM.

- [abort-controller](https://www.npmjs.com/package/abort-controller)[abort-controller.mjs](./abort-controller.mjs)
- [array-every](https://www.npmjs.com/package/array-every)[array-every.mjs](./array-every.mjs)
- [array-flatten](https://www.npmjs.com/package/array-flatten)[array-flatten.mjs](./array-flatten.mjs)
- [array-includes](https://www.npmjs.com/package/array-includes)[array-includes.mjs](./array-includes.mjs)
- [array.prototype.find](https://www.npmjs.com/package/array.prototype.find)[array.prototype.find.mjs](./array.prototype.find.mjs)
- [define-properties](https://www.npmjs.com/package/define-properties)[define-properties.mjs](./define-properties.mjs)
- [define-property](https://www.npmjs.com/package/define-property)[define-property.mjs](./define-property.mjs)
- [es-define-property](https://www.npmjs.com/package/es-define-property)[es-define-property.mjs](./es-define-property.mjs)
- [filter-array](https://www.npmjs.com/package/filter-array)[filter-array.mjs](./filter-array.mjs)
- [for-each](https://www.npmjs.com/package/for-each)[for-each.mjs](./for-each.mjs)
- [function-bind](https://www.npmjs.com/package/function-bind)[function-bind.mjs](./function-bind.mjs)
- [has-own-prop](https://www.npmjs.com/package/has-own-prop)[has-own-prop.mjs](./has-own-prop.mjs)
- [has-own](https://www.npmjs.com/package/has-own)[has-own.mjs](./has-own.mjs)
- [has-property-descriptors](https://www.npmjs.com/package/has-property-descriptors)[has-property-descriptors.mjs](./has-property-descriptors.mjs)
- [has-proto](https://www.npmjs.com/package/has-proto)[has-proto.mjs](./has-proto.mjs)
- [has-symbols](https://www.npmjs.com/package/has-symbols)[has-symbols.mjs](./has-symbols.mjs)
- [has-tostringtag](https://www.npmjs.com/package/has-tostringtag)[has-tostringtag.mjs](./has-tostringtag.mjs)
- [hasown](https://www.npmjs.com/package/hasown)[hasown.mjs](./hasown.mjs)
- [index-of](https://www.npmjs.com/package/index-of)[index-of.mjs](./index-of.mjs)
- [is-even](https://www.npmjs.com/package/is-even)[is-even.mjs](./is-even.mjs)
- [is-nan](https://www.npmjs.com/package/is-nan)[is-nan.mjs](./is-nan.mjs)
- [is-number](https://www.npmjs.com/package/is-number)[is-number.mjs](./is-number.mjs)
- [is-odd](https://www.npmjs.com/package/is-odd)[is-odd.mjs](./is-odd.mjs)
- [is-regexp](https://www.npmjs.com/package/is-regexp)[is-regexp.mjs](./is-regexp.mjs)
- [is-string](https://www.npmjs.com/package/is-string)[is-string.mjs](./is-string.mjs)
- [left-pad](https://www.npmjs.com/package/left-pad)[left-pad.mjs](./left-pad.mjs)
- [pad-left](https://www.npmjs.com/package/pad-left)[pad-left.mjs](./pad-left.mjs)
- [object-assign](https://www.npmjs.com/package/object-assign)[object-assign.mjs](./object-assign.mjs)
- [object-is](https://www.npmjs.com/package/object-is)[object-is.mjs](./object-is.mjs)
- [object-keys](https://www.npmjs.com/package/object-keys)[object-keys.mjs](./object-keys.mjs)
- [object.entries](https://www.npmjs.com/package/object.entries)[object.entries.mjs](./object.entries.mjs)
- [object.values](https://www.npmjs.com/package/object.values)[object.values.mjs](./object.values.mjs)
- [regexp.prototype.flags](https://www.npmjs.com/package/regexp.prototype.flags)[regexp.prototype.flags.mjs](./regexp.prototype.flags.mjs)
3 changes: 3 additions & 0 deletions server/embed/polyfills/npm/abort-controller.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const AbortSignal=globalThis.AbortSignal;
export const AbortController=globalThis.AbortController;
export default AbortController;
2 changes: 2 additions & 0 deletions server/embed/polyfills/npm/array-every.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const every=(a,p,t)=>a.every(p,t);
export default every;
2 changes: 2 additions & 0 deletions server/embed/polyfills/npm/array-flatten.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const flatten=(a,d)=>a.flat(typeof d<"u"?d:Infinity);
export default flatten;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/array-includes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default (a,p,i)=>a.includes(p,i);
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/array.prototype.find.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default (a,p,t)=>a.find(p,t);
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/define-properties.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.defineProperties;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/define-property.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.defineProperty;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/es-define-property.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.defineProperty;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/filter-array.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default (a,p,t)=>a.filter(p,t);
9 changes: 9 additions & 0 deletions server/embed/polyfills/npm/for-each.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default (v, cb) => {
if (Array.isArray(v)) {
v.forEach(cb);
} else if (v && typeof v === "object") {
for (const k in v) {
cb(v[k], k, v);
}
}
};
File renamed without changes.
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/function-bind.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Function.prototype.bind;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/has-own-prop.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default (obj,prop)=>Object.prototype.hasOwnProperty.call(obj,prop);
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/has-own.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.hasOwn??((o,p)=>Object.prototype.hasOwnProperty.call(o,p));
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/has-property-descriptors.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => true;
3 changes: 3 additions & 0 deletions server/embed/polyfills/npm/has-proto.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const foo={bar:{}};
const O=Object;
export default ()=>({__proto__:foo}).bar===foo.bar&&!({__proto__:null}instanceof O);
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/has-symbols.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default ()=>true;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/has-tostringtag.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default ()=>typeof Symbol.toStringTag==="symbol";
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/hasown.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.hasOwn??((o,p)=>Object.prototype.hasOwnProperty.call(o,p));
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/index-of.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default (a,el,i)=>a.indexOf(el,i);
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/is-even.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default n=>(n%2)===0;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/is-nan.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Number.isNaN;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/is-number.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default v=>typeof v==="number";
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/is-odd.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default n=>(n%2)===1;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/is-regexp.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default v=>v instanceof RegExp;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/is-string.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default v=>typeof v==="string";
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/left-pad.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default (s,l,c)=>s.padStart(l,c);
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/object-assign.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.assign;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/object-is.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.is;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/object-keys.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.keys;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/object.entries.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.entries;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/object.values.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default Object.values;
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/pad-left.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default (s,l,c)=>s.padStart(l,c);
1 change: 1 addition & 0 deletions server/embed/polyfills/npm/regexp.prototype.flags.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default r=>r.flags;
26 changes: 4 additions & 22 deletions server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,16 +472,9 @@ func router() rex.Handle {
return rex.Content(pathname, startTime, bytes.NewReader(code))
}

// use embed polyfills/types
if endsWith(pathname, ".js", ".d.ts") && strings.Count(pathname, "/") == 1 {
var data []byte
var err error
isDts := strings.HasSuffix(pathname, ".d.ts")
if isDts {
data, err = embedFS.ReadFile("server/embed/types" + pathname)
} else {
data, err = embedFS.ReadFile("server/embed/polyfills" + pathname)
}
// use embed types
if strings.HasSuffix(pathname, ".d.ts") && strings.Count(pathname, "/") == 1 {
data, err := embedFS.ReadFile("server/embed/types" + pathname)
if err == nil {
ifNoneMatch := ctx.R.Header.Get("If-None-Match")
if ifNoneMatch != "" && ifNoneMatch == globalETag {
Expand All @@ -495,18 +488,7 @@ func router() rex.Handle {
header.Set("ETag", globalETag)
}
}
if isDts {
header.Set("Content-Type", ctTypeScript)
} else {
target := getBuildTargetByUA(userAgent)
code, err := minify(string(data), targets[target], api.LoaderJS)
if err != nil {
return throwErrorJS(ctx, fmt.Sprintf("Transform error: %v", err), false)
}
data = []byte(code)
header.Set("Content-Type", ctJavaScript)
appendVaryHeader(header, "User-Agent")
}
header.Set("Content-Type", ctTypeScript)
return rex.Content(pathname, startTime, bytes.NewReader(data))
}
}
Expand Down
8 changes: 0 additions & 8 deletions test/esm-worker/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,6 @@ Deno.test("esm-worker", { sanitizeOps: false, sanitizeResources: false }, async
assertEquals(res6.headers.get("Content-Type"), "application/javascript; charset=utf-8");
assertEquals(res6.headers.get("Cache-Control"), "public, max-age=31536000, immutable");

const res7 = await fetch(`${workerOrigin}/npm_node-fetch.js`);
assertEquals(res7.status, 200);
assertEquals(res7.headers.get("Content-Type"), "application/javascript; charset=utf-8");
assertEquals(res7.headers.get("Etag"), `W/"${version}"`);
assertEquals(res7.headers.get("Cache-Control"), "public, max-age=86400");
assertStringIncludes(res7.headers.get("Vary")!, "User-Agent");
assertStringIncludes(await res7.text(), "fetch");

const fs = await import(`${workerOrigin}/node/fs.js`);
fs.writeFileSync("foo.txt", "bar", "utf8");
assertEquals(fs.readFileSync("foo.txt", "utf8"), "bar");
Expand Down
2 changes: 1 addition & 1 deletion worker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ function withESMWorker(middleware?: Middleware, cache: Cache = (caches as any).d
pathname === "/run.d.ts" ||
pathname === "/sw" ||
pathname === "/tsx" ||
((pathname.startsWith("/node/") || pathname.startsWith("/npm_")) && pathname.endsWith(".js"))
(pathname.startsWith("/node/") && pathname.endsWith(".js"))
) {
const varyUA = !pathname.endsWith(".ts");
const isChunkjs = pathname.startsWith("/node/chunk-");
Expand Down

3 comments on commit cf60a09

@ljharb
Copy link

@ljharb ljharb commented on cf60a09 Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect; define-properties simply isn’t a polyfill for Object.defineProperties.

@ije
Copy link
Member Author

@ije ije commented on cf60a09 Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the information, going to remove it

@ljharb
Copy link

@ljharb ljharb commented on cf60a09 Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, has-proto can be hardcoded to true in modern engines, and has-tostringtag can be () => true.

you should really tag me in to these kinds of changes, i can at least make sure they're done properly.

Please sign in to comment.