Conversation
There was a problem hiding this comment.
Pull request overview
This is a major release (v137) that includes significant new features, dependency updates, refactoring, and bug fixes. The PR introduces date-based versioning for npm packages, a new ?meta API endpoint, and the esm.sh/x feature for running TypeScript/JSX in browsers. Additionally, it includes the initial release of the CLI tool (v0.1.0) as a separate component with its own versioning scheme.
Changes:
- Introduced date-based package versioning (
yyyy-mm-ddformat) - Added
?metaAPI endpoint for retrieving module build metadata - Refactored
EsmPath.ID()toEsmPath.PackageId()throughout the codebase - Released CLI tool as v0.1.0 with separate versioning from the server
- Updated dependencies (Go modules, Deno, esbuild-internal, gox, rex, etc.)
- Enhanced JSX import source detection to support more frameworks (solid-js, mono-jsx, vue)
- Improved development mode imports handling for React, React-DOM, and Vue
- Updated documentation to use "no-build" terminology consistently
- Removed deprecated
validateModulefunction and associated imports - Simplified web handler by removing base64-encoded import map parameters
Reviewed changes
Copilot reviewed 36 out of 38 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| server/release.go | Updated server version to v137 |
| server/router.go | Refactored to use PackageId() method and added module field to meta API response |
| server/path.go | Renamed ID() method to PackageId() for clarity |
| server/transform.go | Updated JSX import source detection to check for more frameworks |
| server/npmrc.go | Updated getPackageInfoByDate to accept time.Time parameter directly |
| server/build.go | Updated all references from ID() to PackageId() |
| server/build_resolver.go | Updated all references from ID() to PackageId() |
| server/dts_transform.go | Updated all references from ID() to PackageId() |
| server/cjs_module_lexer.go | Updated reference from ID() to PackageId() |
| server/embed/tsx.ts | Updated tsx package version to 1.5.3 |
| internal/npm/npm.go | Refactored IsDateVersion to return parsed time.Time and removed ConvertDateVersionToTime |
| internal/importmap/meta.go | Changed SubPath field to not be serialized in JSON and added Module field |
| internal/importmap/importmap.go | Added Has() method to Imports type |
| internal/deno/deno.go | Updated Deno version to 2.6.9 |
| web/release.go | Changed version to v0.1.0 (separate versioning for web package) |
| web/utils.go | Removed validateModule function and unused imports |
| web/handler.go | Simplified by removing base64-encoded import map parameter handling |
| web/internal/loader.js | Enhanced JSX detection, improved dev imports handling, added isHttpUrl helper |
| web/README.md | Updated documentation with TailwindCSS examples and "no-build" terminology |
| cli/version.go | Set CLI version to v0.1.0 |
| cli/cli.go | Updated help message and command routing |
| cli/command_serve.go | Refactored to separate serve() function |
| cli/command_dev.go | Added new Dev() command |
| cli/command_init.go | Added new Init() command for project scaffolding |
| cli/command_add.go | Fixed terminal line clearing issue |
| cli/command_tidy.go | Updated help message |
| cli/README.md | Updated documentation to reflect new features and installation methods |
| cli/npm/README.md | Updated documentation with improved installation instructions |
| cli/npm/package.json | Fixed "nobuild" to "no-build" typo |
| test/transform/transform.test.ts | Updated test to use proper import map keys |
| test/meta/meta.test.ts | Removed assertion for deprecated subpath field |
| go.mod | Updated Go version to 1.26.0 and updated dependencies |
| go.sum | Updated checksums for new dependency versions |
| Dockerfile | Updated to Go 1.26 and Deno 2.6.9, fixed typo in comment |
| CHANGELOG-SERVER.md | Added v137 release notes |
| CHANGELOG-CLI.md | Added initial CLI changelog for v0.1.0 |
| HOSTING.md | Updated Docker image version reference |
| README.md | Removed deprecated esm.sh/tsx documentation section |
Comments suppressed due to low confidence (1)
CHANGELOG-SERVER.md:15
- The date example uses "2026-01-02", which is in the future relative to the current date (February 15, 2026). This appears to be a typo - the example should use a past date to demonstrate the feature properly, such as "2025-01-02" or another historical date.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
web/internal/loader.js
Outdated
| if (firstSegment === pkgName || firstSegment.startsWith(pkgName + "@")) { | ||
| const version = firstSegment.split("@")[1]; | ||
| if (version) { | ||
| // replace extension `.mjs` with `.development.mjs` |
There was a problem hiding this comment.
There's a double space in the comment: "replace extension .mjs with" should have only one space after .mjs".
web/internal/loader.js
Outdated
| devImports[specifier] = moduleUrl.toString(); | ||
| } else if (specifier.endsWith("/")) { | ||
| // match esm.sh specified route: "https://esm.sh/react@19.2.0&dev/" pattern | ||
| devImports[specifier] = url.replace(version, version + "&dev" ) |
There was a problem hiding this comment.
Missing semicolon at the end of the line. For consistency with the rest of the file, this line should end with a semicolon.
web/internal/loader.js
Outdated
| async function tailwind(_id, content, config) { | ||
| // check if the url is a http url | ||
| function isHttpUrl(url) { | ||
| return typeof url === "string" && url.startsWith("https://") || url.startsWith("http://"); |
There was a problem hiding this comment.
Incorrect operator precedence in the return statement. The expression evaluates as typeof url === "string" && url.startsWith("https://") OR url.startsWith("http://"), which could cause a runtime error if url is not a string. Should be wrapped in parentheses: typeof url === "string" && (url.startsWith("https://") || url.startsWith("http://")).
web/handler.go
Outdated
| return | ||
| } | ||
| imfi, err := os.Lstat(filepath.Join(s.config.AppDir, string(im))) | ||
| indeHtmlStat, err := os.Lstat(filepath.Join(s.config.AppDir, "index.html")) |
There was a problem hiding this comment.
Variable name has a typo: "indeHtmlStat" should be "indexHtmlStat" for clarity and correctness.
web/handler.go
Outdated
| size = fi.Size() | ||
| } | ||
| etag := fmt.Sprintf("w/\"%x-%x-%x-%x%s\"", modTime, size, imfi.ModTime().UnixMilli(), imfi.Size(), s.etagSuffix) | ||
| etag := fmt.Sprintf("w/\"%x-%x-%x-%x%s\"", modTime, size, indeHtmlStat.ModTime().UnixMilli(), indeHtmlStat.Size(), s.etagSuffix) |
There was a problem hiding this comment.
Variable name has a typo: "indeHtmlStat" should be "indexHtmlStat" for clarity and correctness.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 42 out of 154 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
cli/command_init.go:101
- The directory path references "templates/" but the embed directive on line 17 of this file uses "template" (without the 's'). This mismatch will cause the init command to fail because the embedded filesystem won't contain the expected template files. The embed directive should be updated to match:
//go:embed templates
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 42 out of 154 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
test/meta/meta.test.ts:47
- The new
modulefield is added to the meta response (see server/router.go:1703) but there are no tests verifying it. Consider adding an assertion to check thatmeta.moduleis present and has the expected value.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // encodeUrl converts a url.URL to a string without escaping the path. | ||
| func encodeUrl(u *url.URL) string { | ||
| var buf strings.Builder | ||
| n := len(u.Scheme) + 3 + len(u.Host) + len(u.Path) + len(u.RawQuery) | ||
| if u.RawQuery != "" { | ||
| n++ // '?' | ||
| } | ||
| namedExports = make([]string, len(ast.NamedExports)) | ||
| i := 0 | ||
| for name := range ast.NamedExports { | ||
| namedExports[i] = name | ||
| i++ | ||
| buf.Grow(n) | ||
| buf.WriteString(u.Scheme) | ||
| buf.Write([]byte{':', '/', '/'}) | ||
| buf.WriteString(u.Host) | ||
| buf.WriteString(u.Path) | ||
| if u.RawQuery != "" { | ||
| buf.WriteByte('?') | ||
| buf.WriteString(u.RawQuery) | ||
| } | ||
| return | ||
| return buf.String() | ||
| } |
There was a problem hiding this comment.
The encodeUrl function doesn't handle URL fragments (hash). If a URL contains a fragment (e.g., #section), it will be silently dropped. Consider adding support for u.Fragment if fragments are expected in the URLs being encoded.
| im, err := importmap.ParseEsmPath(url) | ||
| if err != nil { | ||
| http.Error(w, "importmap:failed to parse vue import url: "+url, 500) | ||
| } | ||
| options["vueVersion"] = im.Version |
There was a problem hiding this comment.
After calling http.Error for a failed parse, the function continues to execute instead of returning. This will cause options["vueVersion"] to be set to an empty/invalid version. Add a return statement after the error handling on line 570.
| im, err := importmap.ParseEsmPath(url) | ||
| if err != nil { | ||
| http.Error(w, "importmap:failed to parse svelte import url: "+url, 500) | ||
| } | ||
| options["svelteVersion"] = im.Version |
There was a problem hiding this comment.
After calling http.Error for a failed parse, the function continues to execute instead of returning. This will cause options["svelteVersion"] to be set to an empty/invalid version. Add a return statement after the error handling on line 585.
web/handler.go
Outdated
| if importMap.Imports.Has("vue/jsx-runtime") || importMap.Imports.Has("vue/") { | ||
| options["jsxImportSource"] = "vue" | ||
| } |
There was a problem hiding this comment.
This code will panic if importMap is nil. The null check on line 565 doesn't protect line 576. Add a null check before accessing importMap.Imports here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 58 out of 156 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async function tsx(filename, sourceCode, options) { | ||
| const { isDev, react, preact, jsxImportSource } = options ?? {}; | ||
| let lang = filename.endsWith(".md?jsx") ? "jsx" : undefined; | ||
| let code = sourceCode ?? await Deno.readTextFile("." + filename); | ||
| let map = undefined; | ||
| let map = options.map; | ||
| if (filename.endsWith(".svelte") || filename.endsWith(".md?svelte")) { |
There was a problem hiding this comment.
tsx() reads options.map directly. If options is ever null/undefined for a worker call, this will throw at runtime; use optional access (or destructure map from options ?? {}) everywhere options.map is referenced.
| const { transform } = await import("npm:@esm.sh/vue-compiler@1.0.1"); | ||
| const ret = await transform(filename, sourceCode, { | ||
| imports: { "@vue/compiler-sfc": import("npm:@vue/compiler-sfc@" + getPackageVersion(importMap, "vue", "3")) }, | ||
| const ret = await transform(filename, code, { | ||
| imports: { "@vue/compiler-sfc": import("npm:@vue/compiler-sfc@" + vueVersion) }, | ||
| isDev, | ||
| devRuntime: isDev ? "/@vdr" : undefined, |
There was a problem hiding this comment.
transformVue() imports @vue/compiler-sfc@${vueVersion} but vueVersion can be missing/empty, which produces an invalid npm specifier (e.g. @vue/compiler-sfc@undefined). Provide a default (supported major) or throw a clear error when it isn’t provided.
| q := u.Query() | ||
| q.Set("dev", "TRUE") | ||
| u.RawQuery = strings.Replace(q.Encode(), "dev=TRUE", "dev", 1) | ||
| } | ||
| devImports = append(devImports, [2]string{specifier, encodeUrl(u)}) |
There was a problem hiding this comment.
The dev query is added by setting dev=TRUE and then string-replacing it to dev. This is brittle (depends on encoder output and could affect other params); build the query string explicitly so the final URL is always correct.
web/handler.go
Outdated
| if jsxRuntimeUrl != "" && !im.Imports.Has(jsxRuntime+"/jsx-dev-runtime") { | ||
| u, err := url.Parse(jsxRuntimeUrl) | ||
| if err == nil { | ||
| if strings.HasSuffix(u.Path, "/jsx-runtime.mjs") { | ||
| u.Path = u.Path[0:len(u.Path)-16] + "/jsx-dev-runtime.development.mjs" |
There was a problem hiding this comment.
jsxRuntimeUrl can resolve to a relative/local URL from the import map (e.g. /vendor/jsx-runtime.js). Parsing and then serializing it as scheme://host... will yield an invalid :///... URL and overwrite the import map entry. Only apply this dev-runtime rewrite when the resolved URL is an absolute http(s) URL (or handle relative URLs separately).
| importMap: { | ||
| imports: { | ||
| "@jsxRuntime": "https://preact@10.13.2", | ||
| "preact/jsx-runtime": "https://esm.sh/preact@10.13.2/jsx-runtime", | ||
| "preact-render-to-string": "https://esm.sh/preact-render-to-string6.0.2", | ||
| }, |
There was a problem hiding this comment.
The import map URL for preact-render-to-string is missing an @ before the version (.../preact-render-to-string6.0.2). This will likely 404 during transform/bundling; update it to a valid esm.sh package URL.
| im, err := importmap.ParseEsmPath(url) | ||
| if err != nil { | ||
| http.Error(w, "importmap: failed to parse vue import url: "+url, 500) | ||
| return | ||
| } |
There was a problem hiding this comment.
For Vue modules, im.Version can be empty (or ParseEsmPath can fail for non-CDN/local imports). If vueVersion is missing, the loader will later import an invalid @vue/compiler-sfc@.... Consider defaulting to a supported major version and/or only parsing when the import URL is http(s).
web/internal/loader.js
Outdated
| const { isDev, svelteVersion } = options ?? {}; | ||
| const { compile, VERSION } = await import("npm:svelte@" + svelteVersion + "/compiler"); | ||
| const majorVersion = parseInt(VERSION.split(".", 1)[0]); | ||
| if (majorVersion < 5) { | ||
| throw new Error("Unsupported Svelte version: " + VERSION + ". Please use svelte@5 or higher."); |
There was a problem hiding this comment.
transformSvelte() imports svelte@${svelteVersion}/compiler but svelteVersion can be missing/empty, which will break the loader. Provide a default supported version (e.g. 5) or throw a clear error when it can’t be inferred.
| im, err := importmap.ParseEsmPath(url) | ||
| if err != nil { | ||
| http.Error(w, "importmap: failed to parse svelte import url: "+url, 500) | ||
| return | ||
| } |
There was a problem hiding this comment.
Same issue for Svelte: if im.Version is empty (or the import isn’t a parseable CDN URL), svelteVersion becomes missing and the loader will import svelte@undefined. Consider defaulting to a supported major version and/or only parsing when the import URL is http(s).
| if jsxImportSource == "" && (loader == esbuild.LoaderJSX || loader == esbuild.LoaderTSX) && options.importMap != nil { | ||
| var ok bool | ||
| for _, key := range []string{"@jsxRuntime", "@jsxImportSource"} { | ||
| path, resolved := options.importMap.Resolve(key, nil) | ||
| if resolved { | ||
| jsxImportSource = strings.TrimSuffix(path, "/jsx-runtime") | ||
| ok = true | ||
| for _, key := range options.importMap.Imports.Keys() { | ||
| if strings.HasSuffix(key, "/jsx-runtime") { | ||
| jsxImportSource = strings.TrimSuffix(key, "/jsx-runtime") | ||
| break |
There was a problem hiding this comment.
Imports.Keys() returns map keys in an arbitrary order. If multiple */jsx-runtime entries exist, this can pick a different jsxImportSource nondeterministically between runs. Consider checking a deterministic priority list (or sorting) before selecting the runtime.
No description provided.