From 06e8ff1fa7001c112babe0f614c00eadaff250cc Mon Sep 17 00:00:00 2001 From: Je Xia Date: Mon, 6 Jan 2025 08:54:57 +0800 Subject: [PATCH 1/2] Update Github Actions (#983) --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c79a9e21..3cd959c1d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: jobs: deploy: - name: Deploy esm.sh to production + name: Deploy server to production runs-on: ubuntu-latest environment: esm.sh @@ -134,7 +134,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} publish_esmsh_to_npm: - name: Publish "esm.sh" to NPM + name: Publish package "esm.sh" to NPM runs-on: ubuntu-latest environment: release needs: [build_esmsh_cli] @@ -183,7 +183,7 @@ jobs: - name: Extract Release Note run: echo "console.log(require('fs').readFileSync('CHANGELOG.md','utf8').split('## ')[1].slice('${{ github.ref_name }}'.length).trim())" | node > release-note.txt - - name: Create Release + - name: Release uses: softprops/action-gh-release@v2 with: body_path: release-note.txt From 28bb02d2922c1bf690a506116b91e8c67d60d955 Mon Sep 17 00:00:00 2001 From: Je Xia Date: Mon, 6 Jan 2025 10:08:56 +0800 Subject: [PATCH 2/2] Fix external all(*) resolving (#984) --- server/build.go | 72 ++++++++++++++++---------------- server/build_resolver.go | 4 ++ server/module.go | 3 +- test/build-args/external.test.ts | 54 ++++++++++++++---------- 4 files changed, 72 insertions(+), 61 deletions(-) diff --git a/server/build.go b/server/build.go index 225c9b0cc..5afc08edf 100644 --- a/server/build.go +++ b/server/build.go @@ -409,7 +409,7 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include return esbuild.OnResolveResult{Path: path}, nil } - // ban file urls + // ban file: imports if strings.HasPrefix(args.Path, "file:") { return esbuild.OnResolveResult{ Path: fmt.Sprintf("/error.js?type=unsupported-file-dependency&name=%s&importer=%s", strings.TrimPrefix(args.Path, "file:"), ctx.esm.Specifier()), @@ -417,7 +417,7 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include }, nil } - // skip dataurl/http modules + // skip data: and http: imports if strings.HasPrefix(args.Path, "data:") || strings.HasPrefix(args.Path, "https:") || strings.HasPrefix(args.Path, "http:") { return esbuild.OnResolveResult{ Path: args.Path, @@ -441,6 +441,7 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include }, nil } + specifier := normalizeImportSpecifier(args.Path) withTypeJSON := len(args.With) > 0 && args.With["type"] == "json" // it's implicit external @@ -455,9 +456,6 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include }, nil } - // normalize specifier - specifier := normalizeImportSpecifier(args.Path) - // check `?alias` option if len(ctx.args.alias) > 0 && !isRelPathSpecifier(specifier) { pkgName, _, subpath, _ := splitEsmPath(specifier) @@ -469,7 +467,7 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include } } - // resolve specifier using the package `imports` field + // resolve specifier using the `imports` field of package.json if len(ctx.pkgJson.Imports) > 0 { if v, ok := ctx.pkgJson.Imports[specifier]; ok { if s, ok := v.(string); ok { @@ -493,7 +491,7 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include } } - // resolve specifier using the `browser` field + // resolve specifier using the `browser` field of package.json if !isRelPathSpecifier(specifier) && len(ctx.pkgJson.Browser) > 0 && ctx.isBrowserTarget() { if name, ok := ctx.pkgJson.Browser[specifier]; ok { if name == "" { @@ -506,26 +504,14 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include } } - // use `npm:` specifier for `denonext` target if the specifier is in the `forceNpmSpecifiers` list - if forceNpmSpecifiers[specifier] && ctx.target == "denonext" { - version := "" - pkgName, _, subPath, _ := splitEsmPath(specifier) - if pkgName == ctx.esm.PkgName { - version = ctx.esm.PkgVersion - } else if v, ok := ctx.pkgJson.Dependencies[pkgName]; ok && isExactVersion(v) { - version = v - } else if v, ok := ctx.pkgJson.PeerDependencies[pkgName]; ok && isExactVersion(v) { - version = v - } - p := pkgName - if version != "" { - p += "@" + version - } - if subPath != "" { - p += "/" + subPath + // nodejs builtin module + if isNodeBuiltInModule(specifier) { + externalPath, err := ctx.resolveExternalModule(specifier, args.Kind, withTypeJSON, analyzeMode) + if err != nil { + return esbuild.OnResolveResult{}, err } return esbuild.OnResolveResult{ - Path: fmt.Sprintf("npm:%s", p), + Path: externalPath, External: true, }, nil } @@ -539,18 +525,6 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include fullFilepath = path.Join(ctx.wd, "node_modules", specifier) } - // nodejs builtin module - if isNodeBuiltInModule(specifier) { - externalPath, err := ctx.resolveExternalModule(specifier, args.Kind, withTypeJSON, analyzeMode) - if err != nil { - return esbuild.OnResolveResult{}, err - } - return esbuild.OnResolveResult{ - Path: externalPath, - External: true, - }, nil - } - // node native modules do not work via http import if strings.HasSuffix(fullFilepath, ".node") && existsFile(fullFilepath) { return esbuild.OnResolveResult{ @@ -815,6 +789,30 @@ func (ctx *BuildContext) buildModule(analyzeMode bool) (meta *BuildMeta, include } } + // use `npm:` specifier for `denonext` target if the specifier is in the `forceNpmSpecifiers` list + if forceNpmSpecifiers[specifier] && ctx.target == "denonext" { + version := "" + pkgName, _, subPath, _ := splitEsmPath(specifier) + if pkgName == ctx.esm.PkgName { + version = ctx.esm.PkgVersion + } else if v, ok := ctx.pkgJson.Dependencies[pkgName]; ok && isExactVersion(v) { + version = v + } else if v, ok := ctx.pkgJson.PeerDependencies[pkgName]; ok && isExactVersion(v) { + version = v + } + p := pkgName + if version != "" { + p += "@" + version + } + if subPath != "" { + p += "/" + subPath + } + return esbuild.OnResolveResult{ + Path: fmt.Sprintf("npm:%s", p), + External: true, + }, nil + } + // replace some npm modules with browser native APIs var replacement npm_replacements.NpmReplacement var ok bool diff --git a/server/build_resolver.go b/server/build_resolver.go index 2e7b1736d..291c063ee 100644 --- a/server/build_resolver.go +++ b/server/build_resolver.go @@ -589,6 +589,10 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv return specifier, nil } + if ctx.externalAll { + return specifier, nil + } + defer func() { if err == nil && !withTypeJSON { resolvedPathFull := resolvedPath diff --git a/server/module.go b/server/module.go index e51382b2b..96d6f42ee 100644 --- a/server/module.go +++ b/server/module.go @@ -181,9 +181,8 @@ func bundleHttpModule(npmrc *NpmRC, entry string, importMap common.ImportMap, co if u.Scheme == entryUrl.Scheme && u.Host == entryUrl.Host { if (endsWith(u.Path, moduleExts...) || endsWith(u.Path, ".css", ".json", ".vue", ".svelte", ".md")) && !u.Query().Has("url") { return esbuild.OnResolveResult{Path: path, Namespace: "http"}, nil - } else { - return esbuild.OnResolveResult{Path: path, Namespace: "url"}, nil } + return esbuild.OnResolveResult{Path: path, Namespace: "url"}, nil } } } diff --git a/test/build-args/external.test.ts b/test/build-args/external.test.ts index a6bf8349d..182417a8e 100644 --- a/test/build-args/external.test.ts +++ b/test/build-args/external.test.ts @@ -1,31 +1,41 @@ import { assertEquals, assertStringIncludes } from "jsr:@std/assert"; Deno.test("`?external` query", async () => { - const res1 = await fetch("http://localhost:8080/react-dom@18.3.1?external=react"); - const code1 = await res1.text(); - assertStringIncludes(code1, '"/react-dom@18.3.1/X-ZXJlYWN0/denonext/react-dom.mjs"'); - - const res2 = await fetch("http://localhost:8080/*preact@10.23.2/jsx-runtime"); - const code2 = await res2.text(); - assertStringIncludes(code2, '"/*preact@10.23.2/denonext/jsx-runtime.mjs"'); - - const res3 = await fetch("http://localhost:8080/preact@10.23.2/hooks?external=preact"); - const code3 = await res3.text(); - assertStringIncludes(code3, '"/preact@10.23.2/X-ZXByZWFjdA/denonext/hooks.mjs"'); + { + const res = await fetch("http://localhost:8080/react-dom@18.3.1?external=react"); + assertStringIncludes(await res.text(), '"/react-dom@18.3.1/X-ZXJlYWN0/denonext/react-dom.mjs"'); + } + { + const res = await fetch("http://localhost:8080/preact@10.23.2/hooks?external=preact"); + assertStringIncludes(await res.text(), '"/preact@10.23.2/X-ZXByZWFjdA/denonext/hooks.mjs"'); + } + { + const res = await fetch("http://localhost:8080/*preact@10.23.2/jsx-runtime"); + assertStringIncludes(await res.text(), '"/*preact@10.23.2/denonext/jsx-runtime.mjs"'); + } + { + const res = await fetch("http://localhost:8080/*preact@10.23.2/denonext/jsx-runtime.mjs"); + assertStringIncludes(await res.text(), 'from"preact"'); + } + { + const res = await fetch("http://localhost:8080/*preact@10.23.2/denonext/hooks.mjs"); + assertStringIncludes(await res.text(), 'from"preact"'); + } }); Deno.test("drop invalid `?external`", async () => { - const res1 = await fetch("http://localhost:8080/react-dom@18.3.1?target=es2022&external=foo,bar,react"); - const code1 = await res1.text(); - assertStringIncludes(code1, '"/react-dom@18.3.1/X-ZXJlYWN0/es2022/react-dom.mjs"'); - - const res2 = await fetch("http://localhost:8080/react-dom@18.3.1?target=es2022&external=foo,bar,preact"); - const code2 = await res2.text(); - assertStringIncludes(code2, '"/react-dom@18.3.1/es2022/react-dom.mjs"'); - - const res3 = await fetch("http://localhost:8080/react-dom@18.3.1?external=react-dom"); - const code3 = await res3.text(); - assertStringIncludes(code3, '"/react-dom@18.3.1/denonext/react-dom.mjs"'); + { + const res = await fetch("http://localhost:8080/react-dom@18.3.1?target=es2022&external=foo,bar,react"); + assertStringIncludes(await res.text(), '"/react-dom@18.3.1/X-ZXJlYWN0/es2022/react-dom.mjs"'); + } + { + const res = await fetch("http://localhost:8080/react-dom@18.3.1?target=es2022&external=foo,bar,preact"); + assertStringIncludes(await res.text(), '"/react-dom@18.3.1/es2022/react-dom.mjs"'); + } + { + const res = await fetch("http://localhost:8080/react-dom@18.3.1?external=react-dom"); + assertStringIncludes(await res.text(), '"/react-dom@18.3.1/denonext/react-dom.mjs"'); + } }); Deno.test("types with `?external`", async () => {