From cf70f3776e48b1d5921b5eb369eca8f722f92e03 Mon Sep 17 00:00:00 2001 From: "X." Date: Mon, 2 Sep 2024 11:41:25 +0800 Subject: [PATCH] Fix external-all(*) overrides non-external module (#873) * wip * wip * Rewrite `encodeBuildArgs` function * Update testings * Move testings * Revert "Move testings" This reverts commit 04557f717383f8839b60abb02a36fc6143d0a32d. * Move testings --- README.md | 22 ++- server/build.go | 28 +-- server/build_args.go | 30 ++- server/build_args_test.go | 8 +- server/build_resolver.go | 182 +++++++++--------- server/dts_transform.go | 20 +- server/git.go | 2 +- server/npm.go | 28 +-- server/router.go | 176 ++++++++++------- test/bootstrap.ts | 10 +- .../test.tsx => build-args/alias.test.ts} | 0 test/build-args/bundle.test.ts | 28 +++ .../test.tsx => build-args/deps.test.ts} | 0 .../test.ts => build-args/dev.test.ts} | 2 +- .../test.ts => build-args/exports.test.ts} | 2 +- .../test.tsx => build-args/external.test.ts} | 12 ++ .../package-css.test.ts} | 0 test/{raw/test.ts => build-args/raw.test.ts} | 0 .../test.ts => build-args/worker.test.ts} | 0 .../deep-eq.js | 5 - .../deno.json | 12 -- .../test.tsx | 7 - test/bundle-strategy/test.ts | 16 -- .../test.tsx => common/html-to-react.tsx} | 0 test/common/io-ts.test.ts | 3 + test/common/prismjs.test.ts | 12 +- .../test.tsx => common/rc-util.test.ts} | 0 test/dynamic-import/test.ts | 15 -- .../test.ts => gh/fluentui-emoji.test.ts} | 0 test/gh/phospor-icons.test.tsx | 12 ++ .../test.ts => gh/tslib.test.ts} | 2 +- test/html-to-react/deno.json | 8 - test/jsr/hono.test.ts | 9 + test/jsr/std.test.ts | 8 + test/jsx-runtime/deno.json | 10 - test/jsx-runtime/test.tsx | 28 --- test/preact-jsx-runtime/deno.json | 10 - test/preact-jsx-runtime/test.tsx | 17 -- test/preact-with-swr/deno.json | 9 - test/preact/deno.json | 7 +- .../test.tsx => preact/swr.test.tsx} | 6 +- test/preact/test.tsx | 24 ++- test/react-18-dev/deno.json | 8 - test/react-18-stream/deno.json | 8 - .../test.tsx => react-18/dev.test.tsx} | 2 +- test/react-18/{test.tsx => ssr.test.tsx} | 0 .../test.tsx => react-18/stream-ssr.test.tsx} | 0 test/types-versions/deno.json | 6 - test/types-versions/test.ts | 2 +- test/{esm-worker => worker}/pkg-1.0.0.tgz | Bin test/{esm-worker => worker}/test.ts | 2 +- worker/src/index.ts | 16 +- 52 files changed, 388 insertions(+), 426 deletions(-) rename test/{alias-query/test.tsx => build-args/alias.test.ts} (100%) create mode 100644 test/build-args/bundle.test.ts rename test/{deps-query/test.tsx => build-args/deps.test.ts} (100%) rename test/{dev-mode/test.ts => build-args/dev.test.ts} (93%) rename test/{tree-shaking/test.ts => build-args/exports.test.ts} (85%) rename test/{external-query/test.tsx => build-args/external.test.ts} (78%) rename test/{package-css/test.ts => build-args/package-css.test.ts} (100%) rename test/{raw/test.ts => build-args/raw.test.ts} (100%) rename test/{web-worker/test.ts => build-args/worker.test.ts} (100%) delete mode 100644 test/bundle-strategy-with-external-query/deep-eq.js delete mode 100644 test/bundle-strategy-with-external-query/deno.json delete mode 100644 test/bundle-strategy-with-external-query/test.tsx delete mode 100644 test/bundle-strategy/test.ts rename test/{html-to-react/test.tsx => common/html-to-react.tsx} (100%) rename test/{export-all-members/test.tsx => common/rc-util.test.ts} (100%) delete mode 100644 test/dynamic-import/test.ts rename test/{github-assets/test.ts => gh/fluentui-emoji.test.ts} (100%) create mode 100644 test/gh/phospor-icons.test.tsx rename test/{github-module/test.ts => gh/tslib.test.ts} (77%) delete mode 100644 test/html-to-react/deno.json create mode 100644 test/jsr/hono.test.ts create mode 100644 test/jsr/std.test.ts delete mode 100644 test/jsx-runtime/deno.json delete mode 100644 test/jsx-runtime/test.tsx delete mode 100644 test/preact-jsx-runtime/deno.json delete mode 100644 test/preact-jsx-runtime/test.tsx delete mode 100644 test/preact-with-swr/deno.json rename test/{preact-with-swr/test.tsx => preact/swr.test.tsx} (81%) delete mode 100644 test/react-18-dev/deno.json delete mode 100644 test/react-18-stream/deno.json rename test/{react-18-dev/test.tsx => react-18/dev.test.tsx} (92%) rename test/react-18/{test.tsx => ssr.test.tsx} (100%) rename test/{react-18-stream/test.tsx => react-18/stream-ssr.test.tsx} (100%) delete mode 100644 test/types-versions/deno.json rename test/{esm-worker => worker}/pkg-1.0.0.tgz (100%) rename test/{esm-worker => worker}/test.ts (99%) diff --git a/README.md b/README.md index cfb6a013..5b16ab1f 100644 --- a/README.md +++ b/README.md @@ -42,23 +42,27 @@ With [import maps](https://github.com/WICG/import-maps), you can even use bare i - **[NPM](https://npmjs.com)**: ```js // Examples - import React from "https://esm.sh/react"; // 18.3.0 (latest) + import React from "https://esm.sh/react"; // latest import React from "https://esm.sh/react@17"; // 17.0.2 - import React from "https://esm.sh/react@beta"; // 19.0.0-beta-94eed63c49-20240425 - import { renderToString } from "https://esm.sh/react-dom@18.3.0/server"; // submodules + import React from "https://esm.sh/react@beta"; // latest beta + import { renderToString } from "https://esm.sh/react-dom/server"; // sub-modules + ``` +- **[JSR](https://jsr.io)** (starts with `/jsr/`): + ```js + // Examples + import { encodeBase64 } from "https://esm.sh/jsr/@std/encoding@1.0.0/base64"; + import { Hono } from "https://esm.sh/jsr/@hono/hono@4"; ``` - **[GitHub](https://github.com)** (starts with `/gh/`): ```js // Examples - import tslib from "https://esm.sh/gh/microsoft/tslib@2.6.0"; // '2.6.0' is the git tag - // or fetch an asset file from github - fetch("https://esm.sh/gh/microsoft/fluentui-emoji/assets/Party%20popper/Color/party_popper_color.svg"); + import tslib from "https://esm.sh/gh/microsoft/tslib"; // latest + import tslib from "https://esm.sh/gh/microsoft/tslib@2.6.0"; // the version '2.6.0' is a git tag ``` -- **[JSR](https://jsr.io)** (starts with `/jsr/`): +- **[pkg.pr.new](https://pkg.pr.new)** (starts with `/pr/`): ```js // Examples - import { encodeBase64 } from "https://esm.sh/jsr/@std/encoding@1.0.0/base64"; - import { Hono } from "https://esm.sh/jsr/@hono/hono@4"; + import { Bench } from "https://esm.sh/pr/tinylibs/tinybench/tinybench@a832a55"; ``` ### Specifying Dependencies diff --git a/server/build.go b/server/build.go index 752ac60d..c6269d8e 100644 --- a/server/build.go +++ b/server/build.go @@ -128,14 +128,14 @@ func (ctx *BuildContext) Build() (ret BuildResult, err error) { return ctx.buildTypes() } - // query the build result from db + // query previous build ret, ok := ctx.Query() if ok { return } // check if the package is deprecated - if ctx.isDeprecated == "" && !ctx.module.FromGithub && !strings.HasPrefix(ctx.module.PkgName, "@jsr/") { + if ctx.isDeprecated == "" && !ctx.module.GhPrefix && !strings.HasPrefix(ctx.module.PkgName, "@jsr/") { var info PackageJSON info, err = ctx.npmrc.fetchPackageInfo(ctx.module.PkgName, ctx.module.PkgVersion) if err != nil { @@ -196,9 +196,9 @@ func (ctx *BuildContext) install() (err error) { func (ctx *BuildContext) buildModule() (result BuildResult, err error) { // build json - if strings.HasSuffix(ctx.module.SubModule, ".json") { + if strings.HasSuffix(ctx.module.SubModuleName, ".json") { nmDir := path.Join(ctx.wd, "node_modules") - jsonPath := path.Join(nmDir, ctx.module.PkgName, ctx.module.SubModule) + jsonPath := path.Join(nmDir, ctx.module.PkgName, ctx.module.SubModuleName) if existsFile(jsonPath) { var jsonData []byte jsonData, err = os.ReadFile(jsonPath) @@ -220,7 +220,7 @@ func (ctx *BuildContext) buildModule() (result BuildResult, err error) { entry := ctx.resolveEntry(ctx.module) if entry.isEmpty() { - err = fmt.Errorf("could not resolve entry") + err = fmt.Errorf("could not resolve build entry") return } log.Debugf("build(%s): Entry%+v", ctx.module, entry) @@ -257,7 +257,7 @@ func (ctx *BuildContext) buildModule() (result BuildResult, err error) { if err != nil { return } - importUrl := ctx.getImportPath(mod, ctx.getBuildArgsPrefix(mod, false)) + importUrl := ctx.getImportPath(mod, ctx.getBuildArgsPrefix(false)) buf := bytes.NewBuffer(nil) fmt.Fprintf(buf, `export * from "%s";`, importUrl) if r.HasDefaultExport { @@ -276,8 +276,8 @@ func (ctx *BuildContext) buildModule() (result BuildResult, err error) { var input *api.StdinOptions entryModuleSpecifier := ctx.module.PkgName - if ctx.module.SubModule != "" { - entryModuleSpecifier += "/" + ctx.module.SubModule + if ctx.module.SubModuleName != "" { + entryModuleSpecifier += "/" + ctx.module.SubModuleName } if entry.esm == "" { @@ -513,7 +513,7 @@ func (ctx *BuildContext) buildModule() (result BuildResult, err error) { // externalize the _parent_ module // e.g. "react/jsx-runtime" imports "react" - if ctx.module.SubModule != "" && specifier == ctx.module.PkgName && ctx.bundleMode != BundleAll { + if ctx.module.SubModuleName != "" && specifier == ctx.module.PkgName && ctx.bundleMode != BundleAll { externalPath, err := ctx.resolveExternalModule(ctx.module.PkgName, args.Kind) if err != nil { return api.OnResolveResult{}, err @@ -578,10 +578,10 @@ func (ctx *BuildContext) buildModule() (result BuildResult, err error) { if path.Ext(fullFilepath) == "" || !existsFile(fullFilepath) { subPath := utils.CleanPath(moduleSpecifier)[1:] entry := ctx.resolveEntry(Module{ - PkgName: ctx.module.PkgName, - PkgVersion: ctx.module.PkgVersion, - SubModule: toModuleBareName(subPath, true), - SubPath: subPath, + PkgName: ctx.module.PkgName, + PkgVersion: ctx.module.PkgVersion, + SubModuleName: toModuleBareName(subPath, true), + SubPath: subPath, }) if args.Kind == api.ResolveJSImportStatement || args.Kind == api.ResolveJSDynamicImport { if entry.esm != "" { @@ -1290,7 +1290,7 @@ func (ctx *BuildContext) buildTypes() (ret BuildResult, err error) { func (ctx *BuildContext) transformDTS(types string) (err error) { start := time.Now() - buildArgsPrefix := ctx.getBuildArgsPrefix(ctx.module, true) + buildArgsPrefix := ctx.getBuildArgsPrefix(true) n, err := transformDTS(ctx, types, buildArgsPrefix, nil) if err != nil { return diff --git a/server/build_args.go b/server/build_args.go index 3640887e..bdc24c61 100644 --- a/server/build_args.go +++ b/server/build_args.go @@ -58,7 +58,7 @@ func decodeBuildArgs(npmrc *NpmRC, argsString string) (args BuildArgs, err error } else if strings.HasPrefix(p, "c") { args.conditions = append(args.conditions, strings.Split(p[1:], ",")...) } else if strings.HasPrefix(p, "x") { - p, _, _, _, e := validateModulePath(npmrc, p[1:]) + p, _, _, _, e := praseESMPath(npmrc, p[1:]) if e == nil { args.jsxRuntime = &p } @@ -80,14 +80,12 @@ func decodeBuildArgs(npmrc *NpmRC, argsString string) (args BuildArgs, err error return } -func encodeBuildArgs(args BuildArgs, pkg Module, isDts bool) string { +func encodeBuildArgs(args BuildArgs, isDts bool) string { lines := []string{} if len(args.alias) > 0 { var ss sort.StringSlice for from, to := range args.alias { - if from != pkg.PkgName { - ss = append(ss, fmt.Sprintf("%s:%s", from, to)) - } + ss = append(ss, fmt.Sprintf("%s:%s", from, to)) } if len(ss) > 0 { ss.Sort() @@ -97,9 +95,7 @@ func encodeBuildArgs(args BuildArgs, pkg Module, isDts bool) string { if len(args.deps) > 0 { var ss sort.StringSlice for name, version := range args.deps { - if name != pkg.PkgName { - ss = append(ss, fmt.Sprintf("%s@%s", name, version)) - } + ss = append(ss, fmt.Sprintf("%s@%s", name, version)) } if len(ss) > 0 { ss.Sort() @@ -112,9 +108,7 @@ func encodeBuildArgs(args BuildArgs, pkg Module, isDts bool) string { if args.external.Len() > 0 { var ss sort.StringSlice for _, name := range args.external.Values() { - if name != pkg.PkgName { - ss = append(ss, name) - } + ss = append(ss, name) } if len(ss) > 0 { ss.Sort() @@ -189,23 +183,27 @@ func fixBuildArgs(npmrc *NpmRC, installDir string, args *BuildArgs, module Modul args.alias = alias } if len(args.deps) > 0 { - newDeps := map[string]string{} + deps := map[string]string{} for name, version := range args.deps { if module.PkgName == "react-dom" && name == "react" { // react version should be the same as react-dom continue } if depsSet.Has(name) { - newDeps[name] = version + if name != module.PkgName { + deps[name] = version + } } } - args.deps = newDeps + args.deps = deps } if args.external.Len() > 0 { external := NewStringSet() for _, name := range args.external.Values() { if strings.HasPrefix(name, "node:") || depsSet.Has(name) { - external.Add(name) + if name != module.PkgName || module.SubPath != "" { + external.Add(name) + } } } args.external = external @@ -226,7 +224,7 @@ func walkDeps(npmrc *NpmRC, installDir string, module Module, mark *StringSet) ( } if existsFile(pkgJsonPath) { err = utils.ParseJSONFile(pkgJsonPath, &p) - } else if regexpFullVersion.MatchString(module.PkgVersion) || module.FromGithub { + } else if regexpFullVersion.MatchString(module.PkgVersion) || module.GhPrefix { p, err = npmrc.installPackage(module) } else { return nil diff --git a/server/build_args_test.go b/server/build_args_test.go index b4c8674a..a589185c 100644 --- a/server/build_args_test.go +++ b/server/build_args_test.go @@ -16,10 +16,9 @@ func TestEncodeBuildArgs(t *testing.T) { BuildArgs{ alias: map[string]string{"a": "b"}, deps: map[string]string{ - "c": "1.0.0", - "d": "1.0.0", - "e": "1.0.0", - "foo": "1.0.0", // to be ignored + "c": "1.0.0", + "d": "1.0.0", + "e": "1.0.0", }, external: external, exports: exports, @@ -29,7 +28,6 @@ func TestEncodeBuildArgs(t *testing.T) { keepNames: true, ignoreAnnotations: true, }, - Module{PkgName: "foo"}, false, ) args, err := decodeBuildArgs(nil, buildArgsString) diff --git a/server/build_resolver.go b/server/build_resolver.go index a976fdc9..d119949a 100644 --- a/server/build_resolver.go +++ b/server/build_resolver.go @@ -83,29 +83,29 @@ func (ctx *BuildContext) Path() string { return ctx.path } - pkg := ctx.module + module := ctx.module if ctx.target == "types" { - if strings.HasSuffix(pkg.SubPath, ".d.ts") { + if strings.HasSuffix(module.SubPath, ".d.ts") { ctx.path = fmt.Sprintf( "/%s/%s%s", - pkg.PackageName(), - ctx.getBuildArgsPrefix(pkg, true), - pkg.SubPath, + module.PackageName(), + ctx.getBuildArgsPrefix(true), + module.SubPath, ) } else { - ctx.path = "/" + pkg.String() + ctx.path = "/" + module.String() } return ctx.path } - name := strings.TrimSuffix(path.Base(pkg.PkgName), ".js") + name := strings.TrimSuffix(path.Base(module.PkgName), ".js") extname := ".mjs" - if pkg.SubModule != "" { - name = pkg.SubModule + if module.SubModuleName != "" { + name = module.SubModuleName extname = ".js" // workaround for es5-ext "../#/.." path - if pkg.PkgName == "es5-ext" { + if module.PkgName == "es5-ext" { name = strings.ReplaceAll(name, "/#/", "/%23/") } } @@ -120,8 +120,8 @@ func (ctx *BuildContext) Path() string { } ctx.path = fmt.Sprintf( "/%s/%s%s/%s%s", - pkg.PackageName(), - ctx.getBuildArgsPrefix(ctx.module, ctx.target == "types"), + module.PackageName(), + ctx.getBuildArgsPrefix(ctx.target == "types"), ctx.target, name, extname, @@ -132,8 +132,8 @@ func (ctx *BuildContext) Path() string { func (ctx *BuildContext) getImportPath(module Module, buildArgsPrefix string) string { name := strings.TrimSuffix(path.Base(module.PkgName), ".js") extname := ".mjs" - if module.SubModule != "" { - name = module.SubModule + if module.SubModuleName != "" { + name = module.SubModuleName extname = ".js" // workaround for es5-ext "../#/.." path if module.PkgName == "es5-ext" { @@ -157,8 +157,8 @@ func (ctx *BuildContext) getSavepath() string { return normalizeSavePath(ctx.zoneId, path.Join("builds", ctx.Path())) } -func (ctx *BuildContext) getBuildArgsPrefix(pkg Module, isDts bool) string { - if a := encodeBuildArgs(ctx.args, pkg, isDts); a != "" { +func (ctx *BuildContext) getBuildArgsPrefix(isDts bool) string { + if a := encodeBuildArgs(ctx.args, isDts); a != "" { return "X-" + a + "/" } return "" @@ -193,10 +193,10 @@ lookup: pkgJson, err = ctx.npmrc.getPackageInfo(pkgName, v) if err == nil { module = Module{ - PkgName: pkgName, - PkgVersion: pkgJson.Version, - SubPath: subpath, - SubModule: toModuleBareName(subpath, true), + PkgName: pkgName, + PkgVersion: pkgJson.Version, + SubPath: subpath, + SubModuleName: toModuleBareName(subpath, true), } } return @@ -207,10 +207,10 @@ lookup: } if existsFile(pkgJsonPath) && utils.ParseJSONFile(pkgJsonPath, &pkgJson) == nil { module = Module{ - PkgName: pkgName, - PkgVersion: pkgJson.Version, - SubPath: subpath, - SubModule: toModuleBareName(subpath, true), + PkgName: pkgName, + PkgVersion: pkgJson.Version, + SubPath: subpath, + SubModuleName: toModuleBareName(subpath, true), } return } @@ -230,10 +230,10 @@ lookup: pkgJson, err = ctx.npmrc.getPackageInfo(pkgName, version) if err == nil { module = Module{ - PkgName: pkgName, - PkgVersion: pkgJson.Version, - SubPath: subpath, - SubModule: toModuleBareName(subpath, true), + PkgName: pkgName, + PkgVersion: pkgJson.Version, + SubPath: subpath, + SubModuleName: toModuleBareName(subpath, true), } } if err != nil && strings.HasSuffix(err.Error(), " not found") && dts && !strings.HasPrefix(pkgName, "@types/") { @@ -246,8 +246,8 @@ lookup: func (ctx *BuildContext) resolveEntry(module Module) (entry BuildEntry) { pkgDir := ctx.pkgDir - if module.SubModule != "" { - subModule := module.SubModule + if module.SubModuleName != "" { + subModule := module.SubModuleName if endsWith(module.SubPath, ".d.ts", ".d.mts") { entry.dts = "./" + module.SubPath @@ -596,7 +596,7 @@ func (ctx *BuildContext) resolveEntry(module Module) (entry BuildEntry) { entry.cjs = m } } - if module.SubModule == "" { + if module.SubModuleName == "" { if m, ok := ctx.packageJson.Browser["."]; ok && isRelativeSpecifier(m) { if ctx.packageJson.Type == "module" || strings.HasSuffix(m, ".mjs") { entry.esm = m @@ -680,33 +680,35 @@ func (ctx *BuildContext) resolveConditionExportEntry(conditions *OrderedMap, mTy func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.ResolveKind) (resolvedPath string, err error) { defer func() { - fullResolvedPath := resolvedPath - // use relative path for sub-module of current package - if strings.HasPrefix(specifier, ctx.packageJson.Name+"/") { - rp, err := relPath(path.Dir(ctx.Path()), resolvedPath) - if err == nil { - resolvedPath = rp + if err == nil { + fullResolvedPath := resolvedPath + // use relative path for sub-module of current package + if strings.HasPrefix(specifier, ctx.packageJson.Name+"/") { + rp, err := relPath(path.Dir(ctx.Path()), resolvedPath) + if err == nil { + resolvedPath = rp + } + } + // mark the resolved path for _preload_ + if kind != api.ResolveJSDynamicImport { + ctx.imports = append(ctx.imports, [2]string{fullResolvedPath, resolvedPath}) + } + // if it's `require("module")` call + if kind == api.ResolveJSRequireCall { + ctx.requires = append(ctx.requires, [3]string{specifier, fullResolvedPath, resolvedPath}) + resolvedPath = specifier } - } - // mark the resolved path for _preload_ - if kind != api.ResolveJSDynamicImport { - ctx.imports = append(ctx.imports, [2]string{fullResolvedPath, resolvedPath}) - } - // if it's `require("module")` call - if kind == api.ResolveJSRequireCall { - ctx.requires = append(ctx.requires, [3]string{specifier, fullResolvedPath, resolvedPath}) - resolvedPath = specifier } }() // it's current package from github - if npm := ctx.packageJson; ctx.module.FromGithub && (specifier == npm.Name || specifier == npm.PkgName) { + if npm := ctx.packageJson; ctx.module.GhPrefix && (specifier == npm.Name || specifier == npm.PkgName) { pkg := Module{ PkgName: npm.Name, PkgVersion: npm.Version, - FromGithub: true, + GhPrefix: true, } - resolvedPath = ctx.getImportPath(pkg, ctx.getBuildArgsPrefix(pkg, false)) + resolvedPath = ctx.getImportPath(pkg, ctx.getBuildArgsPrefix(false)) return } @@ -732,15 +734,15 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv return } - // it's sub-module of current package + // it's a sub-module of current package if strings.HasPrefix(specifier, ctx.packageJson.Name+"/") { subPath := strings.TrimPrefix(specifier, ctx.packageJson.Name+"/") subPkg := Module{ - PkgName: ctx.module.PkgName, - PkgVersion: ctx.module.PkgVersion, - SubPath: subPath, - SubModule: toModuleBareName(subPath, false), - FromGithub: ctx.module.FromGithub, + PkgName: ctx.module.PkgName, + PkgVersion: ctx.module.PkgVersion, + SubPath: subPath, + SubModuleName: toModuleBareName(subPath, false), + GhPrefix: ctx.module.GhPrefix, } if ctx.subBuilds != nil { b := &BuildContext{ @@ -771,7 +773,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv }() } } - resolvedPath = ctx.getImportPath(subPkg, ctx.getBuildArgsPrefix(subPkg, false)) + resolvedPath = ctx.getImportPath(subPkg, ctx.getBuildArgsPrefix(false)) if ctx.bundleMode == BundleFalse { n, e := utils.SplitByLastByte(resolvedPath, '.') resolvedPath = n + ".nobundle." + e @@ -810,11 +812,11 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv version = ctx.module.PkgVersion } - pkg := Module{ - PkgName: pkgName, - PkgVersion: version, - SubPath: subpath, - SubModule: toModuleBareName(subpath, true), + module := Module{ + PkgName: pkgName, + PkgVersion: version, + SubPath: subpath, + SubModuleName: toModuleBareName(subpath, true), } // resolve alias in dependencies @@ -829,7 +831,7 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv return } if strings.HasPrefix(version, "npm:") { - pkg.PkgName, pkg.PkgVersion, _, _ = splitPkgPath(version[4:]) + module.PkgName, module.PkgVersion, _, _ = splitPkgPath(version[4:]) } else if strings.HasPrefix(version, "git+ssh://") || strings.HasPrefix(version, "git+https://") || strings.HasPrefix(version, "git://") { gitUrl, e := url.Parse(version) if e != nil || gitUrl.Hostname() != "github.com" { @@ -840,37 +842,37 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv if gitUrl.Scheme == "git+ssh" { repo = gitUrl.Port() + "/" + repo } - pkg.FromGithub = true - pkg.PkgName = repo - pkg.PkgVersion = strings.TrimPrefix(url.QueryEscape(gitUrl.Fragment), "semver:") + module.GhPrefix = true + module.PkgName = repo + module.PkgVersion = strings.TrimPrefix(url.QueryEscape(gitUrl.Fragment), "semver:") } else if strings.HasPrefix(version, "github:") || (!strings.HasPrefix(version, "@") && strings.ContainsRune(version, '/')) { repo, fragment := utils.SplitByLastByte(strings.TrimPrefix(version, "github:"), '#') - pkg.FromGithub = true - pkg.PkgName = repo - pkg.PkgVersion = strings.TrimPrefix(url.QueryEscape(fragment), "semver:") + module.GhPrefix = true + module.PkgName = repo + module.PkgVersion = strings.TrimPrefix(url.QueryEscape(fragment), "semver:") } } // fetch the latest tag as the version of the repository - if pkg.FromGithub && pkg.PkgVersion == "" { + if module.GhPrefix && module.PkgVersion == "" { var refs []GitRef - refs, err = listRepoRefs(fmt.Sprintf("https://github.com/%s", pkg.PkgName)) + refs, err = listRepoRefs(fmt.Sprintf("https://github.com/%s", module.PkgName)) if err != nil { return } for _, ref := range refs { if ref.Ref == "HEAD" { - pkg.PkgVersion = ref.Sha[:16] + module.PkgVersion = ref.Sha[:16] break } } } var isFixedVersion bool - if pkg.FromGithub { - isFixedVersion = (valid.IsHexString(pkg.PkgVersion) && len(pkg.PkgVersion) >= 7) || regexpFullVersion.MatchString(strings.TrimPrefix(pkg.PkgVersion, "v")) + if module.GhPrefix { + isFixedVersion = (valid.IsHexString(module.PkgVersion) && len(module.PkgVersion) >= 7) || regexpFullVersion.MatchString(strings.TrimPrefix(module.PkgVersion, "v")) } else { - isFixedVersion = regexpFullVersion.MatchString(pkg.PkgVersion) + isFixedVersion = regexpFullVersion.MatchString(module.PkgVersion) } args := BuildArgs{ alias: ctx.args.alias, @@ -880,33 +882,33 @@ func (ctx *BuildContext) resolveExternalModule(specifier string, kind api.Resolv exports: NewStringSet(), } - err = fixBuildArgs(ctx.npmrc, ctx.wd, &args, pkg) + err = fixBuildArgs(ctx.npmrc, ctx.wd, &args, module) if err != nil { return } if isFixedVersion { buildArgsPrefix := "" - if a := encodeBuildArgs(args, pkg, false); a != "" { + if a := encodeBuildArgs(args, false); a != "" { buildArgsPrefix = "X-" + a + "/" } - resolvedPath = ctx.getImportPath(pkg, buildArgsPrefix) + resolvedPath = ctx.getImportPath(module, buildArgsPrefix) return } - if strings.ContainsRune(pkg.PkgVersion, '|') || strings.ContainsRune(pkg.PkgVersion, ' ') { + if strings.ContainsRune(module.PkgVersion, '|') || strings.ContainsRune(module.PkgVersion, ' ') { // fetch the latest version of the package based on the semver range var p PackageJSON _, p, err = ctx.lookupDep(pkgName+"@"+version, false) if err != nil { return } - pkg.PkgVersion = "^" + p.Version + module.PkgVersion = "^" + p.Version } - resolvedPath = "/" + pkg.String() + resolvedPath = "/" + module.String() // workaround for es5-ext "../#/.." path - if pkg.PkgName == "es5-ext" { + if module.PkgName == "es5-ext" { resolvedPath = strings.ReplaceAll(resolvedPath, "/#/", "/%23/") } params := []string{} @@ -958,7 +960,7 @@ func (ctx *BuildContext) resloveDTS(entry BuildEntry) (string, error) { return fmt.Sprintf( "/%s/%s%s", ctx.module.PackageName(), - ctx.getBuildArgsPrefix(ctx.module, true), + ctx.getBuildArgsPrefix(true), strings.TrimPrefix(entry.dts, "./"), ), nil } @@ -984,10 +986,10 @@ func (ctx *BuildContext) resloveDTS(entry BuildEntry) (string, error) { p, err := ctx.npmrc.getPackageInfo(typesPkgName, version) if err == nil { dtsModule := Module{ - PkgName: typesPkgName, - PkgVersion: p.Version, - SubPath: ctx.module.SubPath, - SubModule: ctx.module.SubModule, + PkgName: typesPkgName, + PkgVersion: p.Version, + SubPath: ctx.module.SubPath, + SubModuleName: ctx.module.SubModuleName, } b := NewBuildContext(ctx.zoneId, ctx.npmrc, dtsModule, ctx.args, "types", BundleFalse, false, false) err := b.install() @@ -1010,7 +1012,7 @@ func (ctx *BuildContext) resloveDTS(entry BuildEntry) (string, error) { } func (ctx *BuildContext) normalizePackageJSON(p PackageJSON) PackageJSON { - if ctx.module.FromGithub { + if ctx.module.GhPrefix { // if the name in package.json is not the same as the repository name if p.Name != ctx.module.PkgName { p.PkgName = p.Name @@ -1037,7 +1039,7 @@ func (ctx *BuildContext) normalizePackageJSON(p PackageJSON) PackageJSON { } // Check if the `SubPath` is the same as the `main` or `module` field of the package.json - if subModule := ctx.module.SubModule; subModule != "" { + if subModule := ctx.module.SubModuleName; subModule != "" { isPkgMainModule := false check := func(s string) bool { return isPkgMainModule || (s != "" && subModule == utils.CleanPath(stripModuleExt(s))[1:]) @@ -1069,7 +1071,7 @@ func (ctx *BuildContext) normalizePackageJSON(p PackageJSON) PackageJSON { isPkgMainModule = (p.Module != "" && check(p.Module)) || (p.Main != "" && check(p.Main)) } if isPkgMainModule { - ctx.module.SubModule = "" + ctx.module.SubModuleName = "" ctx.module.SubPath = "" ctx.path = "" } @@ -1142,7 +1144,7 @@ func (ctx *BuildContext) esmLexer(specifier string) (isESM bool, namedExports [] func matchAsteriskExports(epxortsKey string, pkg Module) (diff string, match bool) { if strings.ContainsRune(epxortsKey, '*') { prefix, _ := utils.SplitByLastByte(epxortsKey, '*') - if subModule := "./" + pkg.SubModule; strings.HasPrefix(subModule, prefix) { + if subModule := "./" + pkg.SubModuleName; strings.HasPrefix(subModule, prefix) { return strings.TrimPrefix(subModule, prefix), true } } diff --git a/server/dts_transform.go b/server/dts_transform.go index 48c9774c..a683c26f 100644 --- a/server/dts_transform.go +++ b/server/dts_transform.go @@ -131,21 +131,21 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker return fmt.Sprintf( "{ESM_CDN_ORIGIN}/%s/%s%s", ctx.module.PackageName(), - ctx.getBuildArgsPrefix(ctx.module, true), + ctx.getBuildArgsPrefix(true), subPath, ), nil } else { entry := ctx.resolveEntry(Module{ - PkgName: depPkgName, - PkgVersion: ctx.module.PkgVersion, - SubPath: subPath, - SubModule: subPath, + PkgName: depPkgName, + PkgVersion: ctx.module.PkgVersion, + SubPath: subPath, + SubModuleName: subPath, }) if entry.dts != "" { return fmt.Sprintf( "{ESM_CDN_ORIGIN}/%s/%s%s", ctx.module.PackageName(), - ctx.getBuildArgsPrefix(ctx.module, true), + ctx.getBuildArgsPrefix(true), strings.TrimPrefix(entry.dts, "./"), ), nil } @@ -192,10 +192,10 @@ func transformDTS(ctx *BuildContext, dts string, buildArgsPrefix string, marker } dtsModule := Module{ - PkgName: p.Name, - PkgVersion: p.Version, - SubPath: subPath, - SubModule: subPath, + PkgName: p.Name, + PkgVersion: p.Version, + SubPath: subPath, + SubModuleName: subPath, } args := BuildArgs{ external: NewStringSet(), diff --git a/server/git.go b/server/git.go index 7d5ca7b9..8a559407 100644 --- a/server/git.go +++ b/server/git.go @@ -75,7 +75,7 @@ func listRepoRefs(repo string) (refs []GitRef, err error) { func ghInstall(wd, name, hash string) (err error) { c := &http.Client{ - Timeout: 30 * time.Second, + Timeout: 5 * time.Minute, } url := fmt.Sprintf(`https://codeload.github.com/%s/tar.gz/%s`, name, hash) res, err := c.Get(url) diff --git a/server/npm.go b/server/npm.go index 22d8c8c5..58d0645a 100644 --- a/server/npm.go +++ b/server/npm.go @@ -24,8 +24,7 @@ import ( const npmRegistry = "https://registry.npmjs.org/" const jsrRegistry = "https://npm.jsr.io/" -// base https://github.com/npm/validate-npm-package-name -var npmNaming = valid.Validator{valid.FromTo{'a', 'z'}, valid.FromTo{'A', 'Z'}, valid.FromTo{'0', '9'}, valid.Eq('.'), valid.Eq('-'), valid.Eq('_')} +var npmNaming = valid.Validator{valid.FromTo{'a', 'z'}, valid.FromTo{'A', 'Z'}, valid.FromTo{'0', '9'}, valid.Eq('_'), valid.Eq('$'), valid.Eq('.'), valid.Eq('-'), valid.Eq('+'), valid.Eq('!'), valid.Eq('~'), valid.Eq('*'), valid.Eq('('), valid.Eq(')')} // NpmPackageVerions defines versions of a NPM package type NpmPackageVerions struct { @@ -498,7 +497,7 @@ func (rc *NpmRC) installPackage(module Module) (pkgJson PackageJSON, err error) attemptMaxTimes := 3 for i := 1; i <= attemptMaxTimes; i++ { - if module.FromGithub { + if module.GhPrefix { err = os.WriteFile(packageJsonFp, []byte(fmt.Sprintf(`{"dependencies":{"%s":"github:%s#%s"}}`, module.PkgName, module.PkgName, module.PkgVersion)), 0644) if err == nil { err = rc.pnpm(installDir) @@ -509,7 +508,10 @@ func (rc *NpmRC) installPackage(module Module) (pkgJson PackageJSON, err error) packageJsonFp := path.Join(installDir, "node_modules", module.PkgName, "package.json") if !existsFile(packageJsonFp) { ensureDir(path.Dir(packageJsonFp)) - err = os.WriteFile(packageJsonFp, utils.MustEncodeJSON(module), 0644) + err = os.WriteFile(packageJsonFp, utils.MustEncodeJSON(PackageJSONRaw{ + Name: module.PkgName, + Version: module.PkgVersion, + }), 0644) } else { var p PackageJSON err = utils.ParseJSONFile(packageJsonFp, &p) @@ -644,18 +646,16 @@ func (rc *NpmRC) createDotNpmRcFile(dir string) error { return os.WriteFile(path.Join(dir, ".npmrc"), buf.Bytes(), 0644) } -// ref https://github.com/npm/validate-npm-package-name -func validatePackageName(name string) bool { - scope := "" - nameWithoutScope := name - if strings.HasPrefix(name, "@") { - scope, nameWithoutScope = utils.SplitByFirstByte(name, '/') - scope = scope[1:] - } - if (scope != "" && !npmNaming.Is(scope)) || (nameWithoutScope == "" || !npmNaming.Is(nameWithoutScope)) || len(name) > 214 { +// based on https://github.com/npm/validate-npm-package-name +func validatePackageName(pkgName string) bool { + if len(pkgName) > 214 { return false } - return true + if strings.HasPrefix(pkgName, "@") { + scope, name := utils.SplitByFirstByte(pkgName, '/') + return npmNaming.Is(scope[1:]) && npmNaming.Is(name) + } + return npmNaming.Is(pkgName) } // added by @jimisaacs diff --git a/server/router.go b/server/router.go index 8c55d0f9..9384471d 100644 --- a/server/router.go +++ b/server/router.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -52,11 +53,12 @@ const ( ) type Module struct { - PkgName string `json:"pkgName"` - PkgVersion string `json:"pkgVersion"` - SubPath string `json:"subPath"` - SubModule string `json:"subModule"` - FromGithub bool `json:"fromGithub"` + PkgName string `json:"pkgName"` + PkgVersion string `json:"pkgVersion"` + SubPath string `json:"subPath"` + SubModuleName string `json:"subModule"` + GhPrefix bool `json:"gh"` + PrPrefix bool `json:"pr"` } func (pkg Module) PackageName() string { @@ -64,7 +66,7 @@ func (pkg Module) PackageName() string { if pkg.PkgVersion != "" && pkg.PkgVersion != "*" && pkg.PkgVersion != "latest" { s += "@" + pkg.PkgVersion } - if pkg.FromGithub { + if pkg.GhPrefix { return "gh/" + s } return s @@ -72,8 +74,8 @@ func (pkg Module) PackageName() string { func (pkg Module) String() string { s := pkg.PackageName() - if pkg.SubModule != "" { - s += "/" + pkg.SubModule + if pkg.SubModuleName != "" { + s += "/" + pkg.SubModuleName } return s } @@ -584,12 +586,11 @@ func router() rex.Handle { cdnOrigin = fmt.Sprintf("https://%s", zoneId) } - // get package info - module, extraQuery, isFixedVersion, isTargetUrl, err := validateModulePath(npmrc, pathname) + module, extraQuery, isFixedVersion, isTargetUrl, err := praseESMPath(npmrc, pathname) if err != nil { status := 500 message := err.Error() - if message == "invalid path" { + if strings.HasPrefix(message, "invalid") { status = 400 } else if strings.HasSuffix(message, " not found") { status = 404 @@ -613,12 +614,12 @@ func router() rex.Handle { } ghPrefix := "" - if module.FromGithub { + if module.GhPrefix { ghPrefix = "/gh" } // redirect `/@types/PKG` to it's main dts file - if strings.HasPrefix(module.PkgName, "@types/") && module.SubModule == "" { + if strings.HasPrefix(module.PkgName, "@types/") && module.SubModuleName == "" { info, err := npmrc.getPackageInfo(module.PkgName, module.PkgVersion) if err != nil { return rex.Status(500, err.Error()) @@ -635,7 +636,7 @@ func router() rex.Handle { } // redirect to main css path for CSS packages - if css := cssPackages[module.PkgName]; css != "" && module.SubModule == "" { + if css := cssPackages[module.PkgName]; css != "" && module.SubModuleName == "" { url := fmt.Sprintf("%s/%s/%s", cdnOrigin, module.String(), css) return rex.Redirect(url, http.StatusFound) } @@ -643,12 +644,12 @@ func router() rex.Handle { // support `https://esm.sh/react?dev&target=es2020/jsx-runtime` pattern for jsx transformer for _, jsxRuntime := range []string{"jsx-runtime", "jsx-dev-runtime"} { if strings.HasSuffix(ctx.R.URL.RawQuery, "/"+jsxRuntime) { - if module.SubModule == "" { - module.SubModule = jsxRuntime + if module.SubModuleName == "" { + module.SubModuleName = jsxRuntime } else { - module.SubModule = module.SubModule + "/" + jsxRuntime + module.SubModuleName = module.SubModuleName + "/" + jsxRuntime } - pathname = fmt.Sprintf("/%s/%s", module.PkgName, module.SubModule) + pathname = fmt.Sprintf("/%s/%s", module.PkgName, module.SubModuleName) ctx.R.URL.RawQuery = strings.TrimSuffix(ctx.R.URL.RawQuery, "/"+jsxRuntime) } } @@ -658,7 +659,7 @@ func router() rex.Handle { // use `?path=$PATH` query to override the pathname if v := query.Get("path"); v != "" { - module.SubModule = utils.CleanPath(v)[1:] + module.SubModuleName = utils.CleanPath(v)[1:] } // check the response type @@ -918,16 +919,16 @@ func router() rex.Handle { for _, v := range strings.Split(query.Get("deps"), ",") { v = strings.TrimSpace(v) if v != "" { - p, _, _, _, err := validateModulePath(npmrc, v) + m, _, _, _, err := praseESMPath(npmrc, v) if err != nil { return rex.Status(400, fmt.Sprintf("Invalid deps query: %v not found", v)) } - if module.PkgName == "react-dom" && p.PkgName == "react" { + if module.PkgName == "react-dom" && m.PkgName == "react" { // make sure react-dom and react are in the same version continue } - if p.PkgName != module.PkgName { - deps[p.PkgName] = p.PkgVersion + if m.PkgName != module.PkgName { + deps[m.PkgName] = m.PkgVersion } } } @@ -991,15 +992,15 @@ func router() rex.Handle { // check if the build args from pathname: `PKG@VERSION/X-${args}/esnext/SUBPATH` isBuildArgsFromPath := false if resType == ResBuild || resType == ResDTS { - a := strings.Split(module.SubModule, "/") + a := strings.Split(module.SubModuleName, "/") if len(a) > 1 && strings.HasPrefix(a[0], "X-") { - module.SubModule = strings.Join(a[1:], "/") + module.SubModuleName = strings.Join(a[1:], "/") args, err := decodeBuildArgs(npmrc, strings.TrimPrefix(a[0], "X-")) if err != nil { return throwErrorJS(ctx, "Invalid build args: "+a[0], false) } module.SubPath = strings.Join(strings.Split(module.SubPath, "/")[1:], "/") - module.SubModule = toModuleBareName(module.SubPath, true) + module.SubModuleName = toModuleBareName(module.SubPath, true) buildArgs = args isBuildArgsFromPath = true } @@ -1017,7 +1018,7 @@ func router() rex.Handle { if resType == ResDTS { findDts := func() (savePath string, fi storage.FileStat, err error) { args := "" - if a := encodeBuildArgs(buildArgs, module, true); a != "" { + if a := encodeBuildArgs(buildArgs, true); a != "" { args = "X-" + a } savePath = normalizeSavePath(zoneId, path.Join(fmt.Sprintf( @@ -1073,11 +1074,11 @@ func router() rex.Handle { // check `?jsx-rutnime` query var jsxRuntime *Module = nil if v := query.Get("jsx-runtime"); v != "" { - p, _, _, _, err := validateModulePath(npmrc, v) + m, _, _, _, err := praseESMPath(npmrc, v) if err != nil { return rex.Status(400, fmt.Sprintf("Invalid jsx-runtime query: %v not found", v)) } - jsxRuntime = &p + jsxRuntime = &m } externalRequire := query.Has("external-require") @@ -1105,12 +1106,12 @@ func router() rex.Handle { noDts := query.Has("no-dts") || query.Has("no-check") // force react/jsx-dev-runtime and react-refresh into `dev` mode - if !isDev && ((module.PkgName == "react" && module.SubModule == "jsx-dev-runtime") || module.PkgName == "react-refresh") { + if !isDev && ((module.PkgName == "react" && module.SubModuleName == "jsx-dev-runtime") || module.PkgName == "react-refresh") { isDev = true } if resType == ResBuild { - a := strings.Split(module.SubModule, "/") + a := strings.Split(module.SubModuleName, "/") if len(a) > 0 { maybeTarget := a[0] if _, ok := targets[maybeTarget]; ok { @@ -1129,7 +1130,7 @@ func router() rex.Handle { basename := strings.TrimSuffix(path.Base(module.PkgName), ".js") if strings.HasSuffix(submodule, ".css") && !strings.HasSuffix(module.SubPath, ".js") { if submodule == basename+".css" { - module.SubModule = "" + module.SubModuleName = "" target = maybeTarget } else { url := fmt.Sprintf("%s/%s", cdnOrigin, module.String()) @@ -1140,7 +1141,7 @@ func router() rex.Handle { if isMjs && submodule == basename { submodule = "" } - module.SubModule = submodule + module.SubModuleName = submodule target = maybeTarget } } @@ -1190,7 +1191,7 @@ func router() rex.Handle { } // redirect to package css from `?css` - if isPkgCss && module.SubModule == "" { + if isPkgCss && module.SubModuleName == "" { if !ret.PackageCSS { return rex.Status(404, "Package CSS not found") } @@ -1291,72 +1292,107 @@ func auth(secret string) rex.Handle { } } -func validateModulePath(rc *NpmRC, pathname string) (pkg Module, extraQuery string, isFixedVersion bool, hasTarget bool, err error) { - fromGithub := strings.HasPrefix(pathname, "/gh/") && strings.Count(pathname, "/") >= 3 - if fromGithub { +func praseESMPath(rc *NpmRC, pathname string) (module Module, extraQuery string, isFixedVersion bool, hasTargetSegment bool, err error) { + // see https://pkg.pr.new + if strings.HasPrefix(pathname, "/pr/") { + pkgName, rest := utils.SplitByFirstByte(pathname[4:], '@') + if rest == "" { + err = errors.New("invalid path") + return + } + version, subPath := utils.SplitByFirstByte(rest, '/') + if !valid.IsHexString(version) || len(version) < 7 { + err = errors.New("invalid path") + return + } + module = Module{ + PkgName: pkgName, + PkgVersion: version, + SubPath: subPath, + SubModuleName: toModuleBareName(subPath, true), + PrPrefix: true, + } + isFixedVersion = true + return + } + + ghPrefix := strings.HasPrefix(pathname, "/gh/") + if ghPrefix { + if len(pathname) == 4 { + err = errors.New("invalid path") + return + } + // add a leading `@` to the package name pathname = "/@" + pathname[4:] - } else if strings.HasPrefix(pathname, "/jsr/@") && strings.Count(pathname, "/") >= 3 { - segs := strings.Split(pathname, "/") - pathname = "/@jsr/" + segs[2][1:] + "__" + segs[3] - if len(segs) > 4 { - pathname += "/" + strings.Join(segs[4:], "/") + } else if strings.HasPrefix(pathname, "/jsr/") { + segs := strings.Split(pathname[5:], "/") + if len(segs) < 2 || !strings.HasPrefix(segs[0], "@") { + err = errors.New("invalid path") + return + } + pathname = "/@jsr/" + segs[0][1:] + "__" + segs[1] + if len(segs) > 2 { + pathname += "/" + strings.Join(segs[2:], "/") } } - pkgName, maybeVersion, subPath, hasTarget := splitPkgPath(pathname) + pkgName, maybeVersion, subPath, hasTargetSegment := splitPkgPath(pathname) if !validatePackageName(pkgName) { err = fmt.Errorf("invalid package name '%s'", pkgName) return } + // strip the leading `@` added before + if ghPrefix { + pkgName = pkgName[1:] + } + version, extraQuery := utils.SplitByFirstByte(maybeVersion, '&') if v, e := url.QueryUnescape(version); e == nil { version = v } - pkg = Module{ - PkgName: pkgName, - PkgVersion: version, - SubPath: subPath, - SubModule: toModuleBareName(subPath, true), - FromGithub: fromGithub, + module = Module{ + PkgName: pkgName, + PkgVersion: version, + SubPath: subPath, + SubModuleName: toModuleBareName(subPath, true), + GhPrefix: ghPrefix, } // workaround for es5-ext "../#/.." path - if pkg.SubModule != "" && pkg.PkgName == "es5-ext" { - pkg.SubModule = strings.ReplaceAll(pkg.SubModule, "/%23/", "/#/") + if module.SubModuleName != "" && module.PkgName == "es5-ext" { + module.SubModuleName = strings.ReplaceAll(module.SubModuleName, "/%23/", "/#/") } - if fromGithub { - // strip the leading `@` - pkg.PkgName = pkg.PkgName[1:] - if (valid.IsHexString(pkg.PkgVersion) && len(pkg.PkgVersion) >= 7) || regexpFullVersion.MatchString(strings.TrimPrefix(pkg.PkgVersion, "v")) { + if ghPrefix { + if (valid.IsHexString(module.PkgVersion) && len(module.PkgVersion) >= 7) || regexpFullVersion.MatchString(strings.TrimPrefix(module.PkgVersion, "v")) { isFixedVersion = true return } var refs []GitRef - refs, err = listRepoRefs(fmt.Sprintf("https://github.com/%s", pkg.PkgName)) + refs, err = listRepoRefs(fmt.Sprintf("https://github.com/%s", module.PkgName)) if err != nil { return } - if pkg.PkgVersion == "" { + if module.PkgVersion == "" { for _, ref := range refs { if ref.Ref == "HEAD" { - pkg.PkgVersion = ref.Sha[:16] + module.PkgVersion = ref.Sha[:7] return } } } else { // try to find the exact tag or branch for _, ref := range refs { - if ref.Ref == "refs/tags/"+pkg.PkgVersion || ref.Ref == "refs/heads/"+pkg.PkgVersion { - pkg.PkgVersion = ref.Sha[:16] + if ref.Ref == "refs/tags/"+module.PkgVersion || ref.Ref == "refs/heads/"+module.PkgVersion { + module.PkgVersion = ref.Sha[:7] return } } // try to find the semver tag var c *semver.Constraints - c, err = semver.NewConstraint(strings.TrimPrefix(pkg.PkgVersion, "semver:")) + c, err = semver.NewConstraint(strings.TrimPrefix(module.PkgVersion, "semver:")) if err == nil { vs := make([]*semver.Version, len(refs)) i := 0 @@ -1374,21 +1410,21 @@ func validateModulePath(rc *NpmRC, pathname string) (pkg Module, extraQuery stri if i > 1 { sort.Sort(semver.Collection(vs)) } - pkg.PkgVersion = vs[i-1].String() + module.PkgVersion = vs[i-1].String() return } } } - err = fmt.Errorf("tag or branch not found") + err = errors.New("tag or branch not found") return } - isFixedVersion = regexpFullVersion.MatchString(pkg.PkgVersion) + isFixedVersion = regexpFullVersion.MatchString(module.PkgVersion) if !isFixedVersion { var p PackageJSON - p, err = rc.fetchPackageInfo(pkgName, pkg.PkgVersion) + p, err = rc.fetchPackageInfo(pkgName, module.PkgVersion) if err == nil { - pkg.PkgVersion = p.Version + module.PkgVersion = p.Version } } return @@ -1426,17 +1462,17 @@ func toModuleBareName(path string, stripIndexSuffier bool) string { return "" } -func splitPkgPath(pathname string) (pkgName string, version string, subPath string, hasTarget bool) { +func splitPkgPath(pathname string) (pkgName string, version string, subPath string, hasTargetSegment bool) { a := strings.Split(strings.TrimPrefix(pathname, "/"), "/") nameAndVersion := "" if strings.HasPrefix(a[0], "@") && len(a) > 1 { nameAndVersion = a[0] + "/" + a[1] subPath = strings.Join(a[2:], "/") - hasTarget = hasTargetSegment(a[2:]) + hasTargetSegment = checkTargetSegment(a[2:]) } else { nameAndVersion = a[0] subPath = strings.Join(a[1:], "/") - hasTarget = hasTargetSegment(a[1:]) + hasTargetSegment = checkTargetSegment(a[1:]) } if len(nameAndVersion) > 0 && nameAndVersion[0] == '@' { pkgName, version = utils.SplitByLastByte(nameAndVersion[1:], '@') @@ -1450,7 +1486,7 @@ func splitPkgPath(pathname string) (pkgName string, version string, subPath stri return } -func hasTargetSegment(segments []string) bool { +func checkTargetSegment(segments []string) bool { if len(segments) < 2 { return false } diff --git a/test/bootstrap.ts b/test/bootstrap.ts index f1c3e510..9d5d7e4f 100755 --- a/test/bootstrap.ts +++ b/test/bootstrap.ts @@ -85,18 +85,16 @@ async function existsFile(path: string): Promise { } if (import.meta.main) { - const rootDir = new URL(import.meta.url).pathname.split("/").slice(0, -2) - .join("/"); - Deno.chdir(rootDir); + Deno.chdir(new URL("../", import.meta.url).pathname); const tests = Deno.args.filter((arg) => !arg.startsWith("-")); const clean = Deno.args.includes("--clean"); if (clean) { try { console.log("Cleaning up..."); await Promise.all([ - Deno.remove("./.esmd/log", { recursive: true }), - Deno.remove("./.esmd/storage", { recursive: true }), - Deno.remove("./.esmd/esm.db"), + Deno.remove(".esmd/log", { recursive: true }), + Deno.remove(".esmd/storage", { recursive: true }), + Deno.remove(".esmd/esm.db"), ]); } catch (_) { // ignore diff --git a/test/alias-query/test.tsx b/test/build-args/alias.test.ts similarity index 100% rename from test/alias-query/test.tsx rename to test/build-args/alias.test.ts diff --git a/test/build-args/bundle.test.ts b/test/build-args/bundle.test.ts new file mode 100644 index 00000000..ff890e9f --- /dev/null +++ b/test/build-args/bundle.test.ts @@ -0,0 +1,28 @@ +import { assertEquals, assertStringIncludes } from "jsr:@std/assert"; + +Deno.test("?bundle", async () => { + const res = await fetch("http://localhost:8080/buffer@6.0.3?bundle&target=es2022"); + res.body?.cancel(); + assertEquals(res.headers.get("x-esm-path")!, "/buffer@6.0.3/es2022/buffer.bundle.mjs"); + const res2 = await fetch(new URL(res.headers.get("x-esm-path")!, "http://localhost:8080")); + assertStringIncludes(await res2.text(), `"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"`); +}); + +Deno.test("?bundle with ?external", async () => { + const res = await fetch("http://localhost:8080/ajv@8.12.0?bundle&external=fast-deep-equal"); + res.body?.cancel(); + assertStringIncludes(res.headers.get("x-esm-path")!, "/X-ZWZhc3QtZGVlcC1lcXVhbA/"); + assertStringIncludes(res.headers.get("x-esm-path")!, "/ajv.bundle.mjs"); + const res2 = await fetch(new URL(res.headers.get("x-esm-path")!, "http://localhost:8080")); + assertStringIncludes(await res2.text(), `from "fast-deep-equal"`); +}); + +Deno.test("?bundle=false", async () => { + const res = await fetch("http://localhost:8080/@pyscript/core@0.3.4/dist/py-terminal-XWbSa71s?bundle=false&target=es2022"); + res.body?.cancel(); + assertEquals(res.headers.get("x-esm-path")!, "/@pyscript/core@0.3.4/es2022/dist/py-terminal-XWbSa71s.nobundle.js"); + const res2 = await fetch(new URL(res.headers.get("x-esm-path")!, "http://localhost:8080")); + const code = await res2.text(); + assertStringIncludes(code, "./core.nobundle.js"); + assertStringIncludes(code, "./error-96hMSEw8.nobundle.js"); +}); diff --git a/test/deps-query/test.tsx b/test/build-args/deps.test.ts similarity index 100% rename from test/deps-query/test.tsx rename to test/build-args/deps.test.ts diff --git a/test/dev-mode/test.ts b/test/build-args/dev.test.ts similarity index 93% rename from test/dev-mode/test.ts rename to test/build-args/dev.test.ts index 9f3bde44..722c7fbc 100644 --- a/test/dev-mode/test.ts +++ b/test/build-args/dev.test.ts @@ -1,6 +1,6 @@ import { assertStringIncludes } from "jsr:@std/assert"; -Deno.test('`NODE_ENV==="development"`', async () => { +Deno.test("?dev", async () => { const res = await fetch("http://localhost:8080/react@18.2.0?dev&target=es2022"); assertStringIncludes(await res.text(), `"/react@18.2.0/es2022/react.development.mjs"`); diff --git a/test/tree-shaking/test.ts b/test/build-args/exports.test.ts similarity index 85% rename from test/tree-shaking/test.ts rename to test/build-args/exports.test.ts index c4ab143a..34543c34 100644 --- a/test/tree-shaking/test.ts +++ b/test/build-args/exports.test.ts @@ -2,6 +2,6 @@ import { assertEquals } from "jsr:@std/assert"; import * as tslib from "http://localhost:8080/tslib?exports=__await,__spread"; -Deno.test("tree-shaking", () => { +Deno.test("?exports", () => { assertEquals(Object.keys(tslib), ["__await", "__spread"]); }); diff --git a/test/external-query/test.tsx b/test/build-args/external.test.ts similarity index 78% rename from test/external-query/test.tsx rename to test/build-args/external.test.ts index b4371ef2..186b56d5 100644 --- a/test/external-query/test.tsx +++ b/test/build-args/external.test.ts @@ -4,6 +4,14 @@ 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/X-Kg/denonext/jsx-runtime.js"'); + + 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.js"'); }); Deno.test("drop invalid `?external`", async () => { @@ -14,6 +22,10 @@ Deno.test("drop invalid `?external`", async () => { 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"'); }); Deno.test("types with `?external`", async () => { diff --git a/test/package-css/test.ts b/test/build-args/package-css.test.ts similarity index 100% rename from test/package-css/test.ts rename to test/build-args/package-css.test.ts diff --git a/test/raw/test.ts b/test/build-args/raw.test.ts similarity index 100% rename from test/raw/test.ts rename to test/build-args/raw.test.ts diff --git a/test/web-worker/test.ts b/test/build-args/worker.test.ts similarity index 100% rename from test/web-worker/test.ts rename to test/build-args/worker.test.ts diff --git a/test/bundle-strategy-with-external-query/deep-eq.js b/test/bundle-strategy-with-external-query/deep-eq.js deleted file mode 100644 index 0d290b7d..00000000 --- a/test/bundle-strategy-with-external-query/deep-eq.js +++ /dev/null @@ -1,5 +0,0 @@ -globalThis.ourDeepEqImported = true; - -export default () => { - throw new Error('not impl') -} \ No newline at end of file diff --git a/test/bundle-strategy-with-external-query/deno.json b/test/bundle-strategy-with-external-query/deno.json deleted file mode 100644 index 0ac43742..00000000 --- a/test/bundle-strategy-with-external-query/deno.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "lib": [ - "dom", - "deno.ns" - ] - }, - "imports": { - "fast-deep-equal": "./deep-eq.js", - "json-schema-traverse": "./not-existed-file-should-throw.js" - } -} diff --git a/test/bundle-strategy-with-external-query/test.tsx b/test/bundle-strategy-with-external-query/test.tsx deleted file mode 100644 index 313bffeb..00000000 --- a/test/bundle-strategy-with-external-query/test.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { assert } from "jsr:@std/assert"; - -import "http://localhost:8080/ajv@8.12.0?bundle&external=fast-deep-equal"; - -Deno.test("external-bundle", async () => { - assert((globalThis as any).ourDeepEqImported === true); -}); diff --git a/test/bundle-strategy/test.ts b/test/bundle-strategy/test.ts deleted file mode 100644 index 6d0e82bb..00000000 --- a/test/bundle-strategy/test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { assertStringIncludes } from "jsr:@std/assert"; - -Deno.test("bundle deps", async () => { - const code = await fetch("http://localhost:8080/buffer@6.0.3/es2022/buffer.bundle.mjs").then((res) => res.text()); - assertStringIncludes( - code, - `"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"`, - ); -}); - -Deno.test("no bundle", async () => { - const code = await fetch("http://localhost:8080/@pyscript/core@0.3.4/es2022/dist/py-terminal-XWbSa71s.nobundle.js") - .then((res) => res.text()); - assertStringIncludes(code, "./core.nobundle.js"); - assertStringIncludes(code, "./error-96hMSEw8.nobundle.js"); -}); diff --git a/test/html-to-react/test.tsx b/test/common/html-to-react.tsx similarity index 100% rename from test/html-to-react/test.tsx rename to test/common/html-to-react.tsx diff --git a/test/common/io-ts.test.ts b/test/common/io-ts.test.ts index dded2dc0..55e56e41 100644 --- a/test/common/io-ts.test.ts +++ b/test/common/io-ts.test.ts @@ -18,6 +18,9 @@ const string = new t.Type( Deno.test("io-ts", async () => { assert(isRight(string.decode("a string"))); assert(!isRight(string.decode(null))); +}); + +Deno.test("io-ts types", async () => { const res = await fetch("http://localhost:8080/fp-ts@2.16.6/lib/Extend.d.ts"); assertEquals(res.status, 200); assertEquals(res.headers.get("Content-Type"), "application/typescript; charset=utf-8"); diff --git a/test/common/prismjs.test.ts b/test/common/prismjs.test.ts index 4902bcec..99e05eb6 100644 --- a/test/common/prismjs.test.ts +++ b/test/common/prismjs.test.ts @@ -1,14 +1,14 @@ -import { assert } from "jsr:@std/assert"; +import { assert, assertEquals } from "jsr:@std/assert"; -import Prism from "http://localhost:8080/prismjs"; -import "http://localhost:8080/prismjs/components/prism-bash"; +import Prism from "http://localhost:8080/prismjs@1.29.0"; +import "http://localhost:8080/prismjs@1.29.0/components/prism-bash"; Deno.test("prismjs", async () => { const code = `var data = 1;`; const html = Prism.highlight(code, Prism.languages.javascript, "javascript"); - assert( - html === - `var data = 1;`, + assertEquals( + html, + `var data = 1;`, ); assert(Object.keys(Prism.languages).includes("bash")); }); diff --git a/test/export-all-members/test.tsx b/test/common/rc-util.test.ts similarity index 100% rename from test/export-all-members/test.tsx rename to test/common/rc-util.test.ts diff --git a/test/dynamic-import/test.ts b/test/dynamic-import/test.ts deleted file mode 100644 index 09c8108e..00000000 --- a/test/dynamic-import/test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { assertEquals, assertStringIncludes } from "jsr:@std/assert"; - -Deno.test("dynamic-import", async () => { - const res = await fetch("http://localhost:8080/esm-monaco@0.0.0-beta.11/lsp/html/setup?target=es2022"); - res.body?.cancel(); - assertEquals(res.status, 200); - - const esmId = res.headers.get("x-esm-path"); - const res2 = await fetch(`http://localhost:8080/${esmId}`); - assertEquals(res.status, 200); - - const code = await res2.text(); - assertStringIncludes(code, `from"../language-features.js"`); - assertStringIncludes(code, `import("./worker.js")`); -}); diff --git a/test/github-assets/test.ts b/test/gh/fluentui-emoji.test.ts similarity index 100% rename from test/github-assets/test.ts rename to test/gh/fluentui-emoji.test.ts diff --git a/test/gh/phospor-icons.test.tsx b/test/gh/phospor-icons.test.tsx new file mode 100644 index 00000000..b221f184 --- /dev/null +++ b/test/gh/phospor-icons.test.tsx @@ -0,0 +1,12 @@ +/* @jsxImportSource http://localhost:8080/react@18.2.0 */ + +import { assertStringIncludes } from "jsr:@std/assert"; + +import { Airplay } from "http://localhost:8080/gh/phosphor-icons/react@v2.1.5/src/csr/Airplay.tsx?deps=react@18.2.0"; +import { renderToString } from "http://localhost:8080/react-dom@18.2.0/server"; + +Deno.test("rendering a svg from github", async () => { + const svg = renderToString(); + assertStringIncludes(svg, ""); +}); diff --git a/test/github-module/test.ts b/test/gh/tslib.test.ts similarity index 77% rename from test/github-module/test.ts rename to test/gh/tslib.test.ts index 9b9937ac..e272e453 100644 --- a/test/github-module/test.ts +++ b/test/gh/tslib.test.ts @@ -2,6 +2,6 @@ import { assertExists } from "jsr:@std/assert"; import * as tslib from "http://localhost:8080/gh/microsoft/tslib"; -Deno.test("github module", async () => { +Deno.test("tslib from github", async () => { assertExists(tslib.__await); }); diff --git a/test/html-to-react/deno.json b/test/html-to-react/deno.json deleted file mode 100644 index 72517779..00000000 --- a/test/html-to-react/deno.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "lib": [ - "dom", - "deno.ns" - ] - } -} \ No newline at end of file diff --git a/test/jsr/hono.test.ts b/test/jsr/hono.test.ts new file mode 100644 index 00000000..61750a33 --- /dev/null +++ b/test/jsr/hono.test.ts @@ -0,0 +1,9 @@ +import { assertEquals } from "jsr:@std/assert"; + +import { Hono } from "https://esm.sh/jsr/@hono/hono@4"; + +Deno.test("hono", async () => { + const hono = new Hono(); + hono.get("/", (ctx) => ctx.text("Hello, Hono!")); + assertEquals(await (await hono.fetch(new Request("http://localhost/"))).text(), "Hello, Hono!"); +}); diff --git a/test/jsr/std.test.ts b/test/jsr/std.test.ts new file mode 100644 index 00000000..a4bfdfd6 --- /dev/null +++ b/test/jsr/std.test.ts @@ -0,0 +1,8 @@ +import { assertEquals } from "jsr:@std/assert"; + +import { decodeBase64, encodeBase64 } from "https://esm.sh/jsr/@std/encoding@1.0.0/base64"; + +Deno.test("@std/encoding", async () => { + assertEquals(encodeBase64("hello"), "aGVsbG8="); + assertEquals(new TextDecoder().decode(decodeBase64("aGVsbG8=")), "hello"); +}); diff --git a/test/jsx-runtime/deno.json b/test/jsx-runtime/deno.json deleted file mode 100644 index a98ac05d..00000000 --- a/test/jsx-runtime/deno.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "http://localhost:8080/react@18.2.0", - "lib": [ - "dom", - "deno.ns" - ] - } -} \ No newline at end of file diff --git a/test/jsx-runtime/test.tsx b/test/jsx-runtime/test.tsx deleted file mode 100644 index 5f44ea03..00000000 --- a/test/jsx-runtime/test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { assert, assertStringIncludes } from "jsr:@std/assert"; - -import { Airplay } from "http://localhost:8080/gh/phosphor-icons/react@v2.1.5/src/csr/Airplay.tsx?deps=react@18.2.0"; -import { renderToString } from "http://localhost:8080/react-dom@18.2.0/server"; - -Deno.test("jsx-runtime", async () => { - const html = renderToString( -
-

Hi :)

- , - ); - assert( - typeof html === "string" && - html.includes("

Hi :)

") && - html.includes(""), - ); -}); - -Deno.test("react-jsx-runtime deps", async () => { - const res = await fetch("http://localhost:8080/react@18.2.0/esnext/jsx-runtime.js"); - assertStringIncludes(await res.text(), `from "/react@18.2.0/esnext/react.mjs"`); -}); - -Deno.test("rendering a svg from github.com", async () => { - const svg = renderToString(); - assertStringIncludes(svg, ""); -}); diff --git a/test/preact-jsx-runtime/deno.json b/test/preact-jsx-runtime/deno.json deleted file mode 100644 index 626aac51..00000000 --- a/test/preact-jsx-runtime/deno.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "http://localhost:8080/preact@10.14.0", - "lib": [ - "dom", - "deno.ns" - ] - } -} diff --git a/test/preact-jsx-runtime/test.tsx b/test/preact-jsx-runtime/test.tsx deleted file mode 100644 index e55ee4b7..00000000 --- a/test/preact-jsx-runtime/test.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { assert } from "jsr:@std/assert"; - -import { useState } from "http://localhost:8080/preact@10.14.0/hooks"; -import render from "http://localhost:8080/preact-render-to-string@6.0.3?deps=preact@10.14.0"; - -Deno.test("preact-jsx-runtime", () => { - const App = () => { - const [message] = useState("Hi :)"); - return ( -
-

{message}

- - ); - }; - const html = render(); - assert(html == "

Hi :)

"); -}); diff --git a/test/preact-with-swr/deno.json b/test/preact-with-swr/deno.json deleted file mode 100644 index ac752668..00000000 --- a/test/preact-with-swr/deno.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "lib": [ - "dom", - "deno.ns" - ], - "jsxFactory": "h" - } -} \ No newline at end of file diff --git a/test/preact/deno.json b/test/preact/deno.json index ac752668..626aac51 100644 --- a/test/preact/deno.json +++ b/test/preact/deno.json @@ -1,9 +1,10 @@ { "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "http://localhost:8080/preact@10.14.0", "lib": [ "dom", "deno.ns" - ], - "jsxFactory": "h" + ] } -} \ No newline at end of file +} diff --git a/test/preact-with-swr/test.tsx b/test/preact/swr.test.tsx similarity index 81% rename from test/preact-with-swr/test.tsx rename to test/preact/swr.test.tsx index b411ceb6..619c32e6 100644 --- a/test/preact-with-swr/test.tsx +++ b/test/preact/swr.test.tsx @@ -1,6 +1,4 @@ -import { assert } from "jsr:@std/assert"; - -import { h } from "http://localhost:8080/preact@10.7.2"; +import { assertEquals } from "jsr:@std/assert"; import render from "http://localhost:8080/preact-render-to-string@5.2.0?deps=preact@10.7.2"; import useSWR from "http://localhost:8080/swr@1.3.0?alias=react:preact/compat&deps=preact@10.7.2"; @@ -24,5 +22,5 @@ Deno.test("preact-swr", () => { ); }; const html = render(); - assert(html == "

just now

"); + assertEquals(html, "

just now

"); }); diff --git a/test/preact/test.tsx b/test/preact/test.tsx index c074dce9..7fb513ca 100644 --- a/test/preact/test.tsx +++ b/test/preact/test.tsx @@ -1,12 +1,26 @@ import { assertEquals } from "jsr:@std/assert"; - -import { h } from "http://localhost:8080/preact@10.14.0"; +import { h } from "http://localhost:8080/preact@10.7.2"; import { useState } from "http://localhost:8080/preact@10.14.0/hooks"; import render from "http://localhost:8080/preact-render-to-string@6.0.3?deps=preact@10.14.0"; -Deno.test("preact", () => { +Deno.test("preact-jsx", () => { + const div = h("div", null, h("h1", null, "Hey")); + const divx = ( +
+

Hey

+
+ ); + assertEquals(div.type, "div"); + assertEquals(div.props.children.type, "h1"); + assertEquals(div.props.children.props.children, "Hey"); + assertEquals(divx.type, "div"); + assertEquals(divx.props.children.type, "h1"); + assertEquals(divx.props.children.props.children, "Hey"); +}); + +Deno.test("preact-render-to-string", () => { const App = () => { - const [message] = useState("Hi :)"); + const [message] = useState("Hey"); return (

{message}

@@ -14,5 +28,5 @@ Deno.test("preact", () => { ); }; const html = render(); - assertEquals(html, "

Hi :)

"); + assertEquals(html, "

Hey

"); }); diff --git a/test/react-18-dev/deno.json b/test/react-18-dev/deno.json deleted file mode 100644 index 72517779..00000000 --- a/test/react-18-dev/deno.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "lib": [ - "dom", - "deno.ns" - ] - } -} \ No newline at end of file diff --git a/test/react-18-stream/deno.json b/test/react-18-stream/deno.json deleted file mode 100644 index 72517779..00000000 --- a/test/react-18-stream/deno.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "lib": [ - "dom", - "deno.ns" - ] - } -} \ No newline at end of file diff --git a/test/react-18-dev/test.tsx b/test/react-18/dev.test.tsx similarity index 92% rename from test/react-18-dev/test.tsx rename to test/react-18/dev.test.tsx index 17998452..a21efa77 100644 --- a/test/react-18-dev/test.tsx +++ b/test/react-18/dev.test.tsx @@ -3,7 +3,7 @@ import { assert } from "jsr:@std/assert"; import React from "http://localhost:8080/react@18&dev"; import { renderToString } from "http://localhost:8080/react-dom@18&dev/server"; -Deno.test("react@18(dev)", () => { +Deno.test("react@18?dev", () => { const html = renderToString(

Hi :)

diff --git a/test/react-18/test.tsx b/test/react-18/ssr.test.tsx similarity index 100% rename from test/react-18/test.tsx rename to test/react-18/ssr.test.tsx diff --git a/test/react-18-stream/test.tsx b/test/react-18/stream-ssr.test.tsx similarity index 100% rename from test/react-18-stream/test.tsx rename to test/react-18/stream-ssr.test.tsx diff --git a/test/types-versions/deno.json b/test/types-versions/deno.json deleted file mode 100644 index bff75595..00000000 --- a/test/types-versions/deno.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "imports": { - "assert": "jsr:@std/assert", - "~/": "http://localhost:8080/" - } -} diff --git a/test/types-versions/test.ts b/test/types-versions/test.ts index dcce1709..5df8a999 100644 --- a/test/types-versions/test.ts +++ b/test/types-versions/test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertStringIncludes } from "assert"; +import { assertEquals, assertStringIncludes } from "jsr:@std/assert"; Deno.test("typesVersions", async () => { const res = await fetch("http://localhost:8080/redux-saga@1.2.0/index.d.ts"); diff --git a/test/esm-worker/pkg-1.0.0.tgz b/test/worker/pkg-1.0.0.tgz similarity index 100% rename from test/esm-worker/pkg-1.0.0.tgz rename to test/worker/pkg-1.0.0.tgz diff --git a/test/esm-worker/test.ts b/test/worker/test.ts similarity index 99% rename from test/esm-worker/test.ts rename to test/worker/test.ts index 1cadd7be..51cb3214 100644 --- a/test/esm-worker/test.ts +++ b/test/worker/test.ts @@ -162,7 +162,7 @@ Deno.test("esm-worker", { sanitizeOps: false, sanitizeResources: false }, async res3.body?.cancel(); assertEquals(res3.status, 400); - const res4 = await fetch(`${workerOrigin}/react~dom@18`); + const res4 = await fetch(`${workerOrigin}/react>dom@18`); res4.body?.cancel(); assertEquals(res4.status, 400); diff --git a/worker/src/index.ts b/worker/src/index.ts index a38cc203..d47f58b4 100644 --- a/worker/src/index.ts +++ b/worker/src/index.ts @@ -13,9 +13,9 @@ const defaultNpmRegistry = "https://registry.npmjs.org"; const jsrNpmRegistry = "https://npm.jsr.io"; const ccImmutable = "public, max-age=31536000, immutable"; -const regexpNpmNaming = /^[a-zA-Z0-9][\w\-\.]*$/; -const regexpFullVersion = /^\d+\.\d+\.\d+[\w\-\.\+]*$/; -const regexpCommitish = /^[a-f0-9]{10,}$/; +const regexpNpmNaming = /^[\w\-+.$!*~()]*$/; +const regexpFullVersion = /^\d+\.\d+\.\d+[\w\-.+]*$/; +const regexpCommitish = /^[a-f0-9]{7,}$/; const regexpLegacyVersionPrefix = /^\/v\d+\//; const regexpLegacyBuild = /^\/~[a-f0-9]{40}$/; const regexpLocSuffix = /:\d+:\d+$/; @@ -127,7 +127,7 @@ async function fetchAsset(req: Request, ctx: Context, env: Env, pathname: string } /** fetch build files like *.js, *.mjs, *.css, etc. */ -async function fetchBuild(req: Request, env: Env, ctx: Context, pathname: string, query?: string): Promise { +async function fetchBuildDist(req: Request, env: Env, ctx: Context, pathname: string, query?: string): Promise { const R2 = env.R2; const isRaw = ctx.url.searchParams.has("raw"); const isDts = isDtsFile(pathname); @@ -305,7 +305,7 @@ function withESMWorker(middleware?: Middleware, cache: Cache = (caches as any).d return ctx.withCache((target) => { let query = target ? "?target=" + target : undefined; if (isChunkjs) { - return fetchBuild(req, env, ctx, pathname, query); + return fetchBuildDist(req, env, ctx, pathname, query); } return fetchOrigin(req, env, ctx, pathname, query); }, { @@ -368,7 +368,7 @@ function withESMWorker(middleware?: Middleware, cache: Cache = (caches as any).d // if it's a singleton build module which is created by https://esm.sh/tsx if (pathname.startsWith("/+") && (pathname.endsWith(".mjs") || pathname.endsWith(".mjs.map"))) { return ctx.withCache(() => { - return fetchBuild(req, env, ctx, pathname); + return fetchBuildDist(req, env, ctx, pathname); }); } @@ -667,7 +667,7 @@ function withESMWorker(middleware?: Middleware, cache: Cache = (caches as any).d if (isTargetUrl || isDtsFile(subPath)) { return ctx.withCache(() => { const pathname = `${prefix}/${pkgFullname}@${packageVersion}${subPath}`; - return fetchBuild(req, env, ctx, pathname); + return fetchBuildDist(req, env, ctx, pathname); }); } @@ -679,7 +679,7 @@ function withESMWorker(middleware?: Middleware, cache: Cache = (caches as any).d if (target) { params.set("target", target); } - return fetchBuild(req, env, ctx, pathname, "?" + params.toString()); + return fetchBuildDist(req, env, ctx, pathname, "?" + params.toString()); }, { varyUA: true }); }