-
Notifications
You must be signed in to change notification settings - Fork 5.5k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
As a library author, how do I dual publish to both node and deno? #3196
Comments
For Deno, you don't need npm at all. Having it located somewhere on a CDN which the The biggest "problem" with dual support would be that modules have to end in the extension in Deno. Also Node.js doesn't support TypeScript directly, and doesn't support ESM (without enabling experimental modules). So you would have to have a build a version that would work frictionlessly in Node.js and have that be your distributable to npm. I'm personally not aware of anyone who has created a good build script for Deno If you are using |
Ahhh yeah the big issue is the requirement of file extensions for deno. I like your suggestion of Regardless of how successful deno becomes, I see dual-supporting both node and deno as pretty much necessary for most library authors. Related: microsoft/TypeScript#27481 |
@brainkim regarding the modules resolution, there is also: microsoft/TypeScript#33437 that would be more of an ideal, but getting Node.js to support it... Though Node.js is moving towards extension required modules under the experimental ES support, there would still be a need to change the extensions on the emitted code, and if TypeScript ( |
I think for the purpose of creating "isomorphic" Deno libs and CLIs it makes sense to implement sort of Node.js polyfill for all the Deno's built-in APIs, then bundle your tool using const {Deno, TextDecoder, TextEncoder, ...} = require('deno-polyfill');
/// deno generated bundle probably it also makes sense to convert AMD modules in the original bundle to commonjs |
allegedly this is what @YounGoat's deno package on npm is supposed to do... |
Okay so
This is interesting, but I’m also interested in a simpler use-case where I use 0 deno builtins and 0 node builtins (pretty much vanilla typescript). |
@brainkim Deno can consume a single JS file or ES modules if you need imports. It doesn’t know about AMD except as an output from the bundle command. (The AMD bit is really throwing people off - we should modify deno bundle to use https://github.com/denoland/deno/blob/master/deno_typescript/amd_runtime.js to execute the main module.) |
@ry we talked about it for Bundling v2 (#2475), but it does feel like embedding the runner directly in the bundle so it is a totally self contained script is warranted and knock a few features off the v2 list. That way we can just say "it generates a standalone script" and let people forget about the implementation. I can start working on this. |
The whole thing would be easier if |
@tunnckoCore again, people are obsessing about something they shouldn't obsess about. 😁 UMD specifically would be useless. That is, depending on the implementation, 2 different module formats and an ability to load as a global script, but when you only need one module format that is loaded into an environment that doesn't support CommonJS for good reason. If you really want to know the reason though, it goes something like this:
So we were left with the scenarios of:
We opted for the last. The only value in the first two is to be able to say we weren't using AMD, which seems like a very odd thing to do. AMD came into being for some valid good and valid reasons, and ESM doesn't solve all of them. There are very good reasons why ESM isn't concatenatable, and I respect that, but it does mean in a lot of cases, we will be stuck with some form of bundlers in perpetuity (and likely AMD). |
@kitsonk I would consider System.js as a bundle output as well. I think typescript supports it as an output (though last I checked support is not great and System.js can be tricky to set up), it’s more closely aligned with ESmodules, and But most of this is unrelated to this issue, which is, given a pure typescript codebase without dependencies, how do I add support for deno consumers? The two approaches would be:
|
@brainkim , last week or two I'm thinking about the second too. Write Nodejs (js/ts), so need for deno vscode/typescript plugins. Output esm and cjs formats - e.g. import koko from 'https://unpkg.com/@tunnckocore/kokoko?module';
koko('sasas', 200);
// should error, because both arguments must be `number` I have two dummy modules, that I'm playing with: @tunnckocore/kokoko and @tunnckocore/qwqwqw check them on unpkg. The interesting thing is that UNPKG is helping us when using the |
Ha... I already wrote to that issue, haha. Just thought about another idea that spawned after looking around the other "compat" issues and discussions, mainly triggered to look on them today because just seen that And second, the another possible idea:
** another variant of that step is to directly URL import/resolve from Deno and transform that TS to JS, BUT that way consumers of that nodejs code source may have problems if there are any significant differences or bugs or behaviors between what Deno gives, for example, for the Write mainly in Deno. And if you want to support both Browsers and Node (and Deno, hence the source) you can use a small smart CLI that can be created, nothing fancy, just a thin translation layer using @rollup so we can output whatever format we want. It's would probably be best to be written in Node.js and then bundled as single file binary. |
I think the best course of action is just to convince typescript peeps to allow explicit file names. |
Don't know if this is the best place to post this, but I wanted to share the pattern I came up with for running code on both node and deno. I append this to the end of a
Not up to the needs of library authors, I know, but it's useful to have such a recipe for quick scripts that might be run on either host. |
I expanded that recipe a bit, folding in the options parsing from |
I’ve finally made my first foray with Deno. The unpkg If I transpile to ESNext I worry that I will mess with dependents who don’t transpile their sources (for things like async generators). And ideally I would be able to reference my modules locally for local development, but this second library I’m working on has a single dependency (also owned by me). Anyone have any updates on best practices? |
Coming up with a concrete story for dual publishing to npm and deno would get me to switch. unpkg might work for a while but it would suck to have to publish for local development. |
@brainkim take a look at your package on Pika.dev: https://www.pika.dev/npm/@bikeshaving/crank Try loading it from there... It "should" serve up the |
@kitsonk Tried out the link and got a 404. The module which pika points to doesn’t seem to exist. If you’re saying pika can allow me to dual publish to npm and deno then I’ll check it out and see if I can get it to work. I was initially turned off by references to some all-in-one editing solution but I’m willing to give it a shot. Figuring out local development might still be a pain though. |
Go to that page, there is a different URL listed on the page. Pika CDN is a good CDN for both browsers and Deno for packages that contain ESM modules and types. Hopefully @axetroy can help with the development environment end for Vscode. The type information merging is a relatively new feature and I don't know if his plugin understands it well. The information is in the Deno cache, so the plugin could support it. |
https://cdn.pika.dev/@bikeshaving/crank@^0.1.0-beta.4 responds with a file like this: /*
Pika CDN - @bikeshaving/[email protected]
https://www.pika.dev/npm/@bikeshaving/crank
How it works:
1. Import this package to your site using this file/URL (see examples).
2. Your web browser will fetch the browser-optimized code from the export statements below.
3. Don't directly import the export URLs below: they're optimized to your browser specifically and may break other users.
Examples:
- import {Component, render} from 'https://cdn.pika.dev/preact@^10.0.0';
- import {Component, render} from 'https://cdn.pika.dev/[email protected]';
- import {Component, render} from 'https://cdn.pika.dev/preact@next';
- import {Component, render} from 'https://cdn.pika.dev/preact';
- const {Component, render} = await import('https://cdn.pika.dev/preact@^10.0.0');
Learn more: https://www.pika.dev/cdn
*/
export * from '/-/@bikeshaving/[email protected]/dist=es2019/crank.js';
export {default} from '/-/@bikeshaving/[email protected]/dist=es2019/crank.js'; https://cdn.pika.dev/-/@bikeshaving/[email protected]/dist=es2019/crank.js responds with a 404 with the following message:
This probably has something to do with the dash or something. This is a maze with lots of possible paths, and I’m not sure how pika helps over something like unpkg, but I’m happy for any assistance. Like I said, creating a concrete story for devs who want to dual publish is of paramount importance. Especially if you want to pick up authors who author “universal” modules, deno offers so much in its reproduction of standard DOM apis. |
Try without the caret: https://cdn.pika.dev/@bikeshaving/[email protected] |
Same thing: The problem probably is the dash is being parsed weirdly by pika. |
For what it’s worth, Deno finding types via an |
What strange module is this? export * from '/-/@bikeshaving/[email protected]/dist=es2019/crank.js'; |
I've created make-deno-edition to make npm packages written in typescript compatible with deno - is working on badges - usage proof of this here https://repl.it/@balupton/badges-deno - has been used now to make 32 node packages compatible with deno - you can use project to automatically generate the readme instructions for the deno edition - and can use boundation to automatically scaffold your projects to automate the entire process - start-of-week is an example where different entries are used for node, deno, and web browsers |
Here are some techniques I’ve discovered for node -> deno publishing while trying to make my frontend framework deno compatible.
import {
Children,
Context,
Element as CrankElement,
ElementValue,
Portal,
Renderer,
// @ts-ignore: explicit ts
} from "./crank.ts"; You can add a
// @deno-types="https://unpkg.com/@bikeshaving/[email protected]/index.d.ts"
import {createElement} from "https://unpkg.com/@bikeshaving/[email protected]/index.js";
// @deno-types="https://unpkg.com/@bikeshaving/[email protected]/html.d.ts"
import {renderer} from "https://unpkg.com/@bikeshaving/[email protected]/html.js"; You can import modules without cooperation from the package author by importing the JS equivalents, and referencing the d.ts types using a If you are the package author, you can use the following rollup plugin snippet using the package "magic-string" to prepend a triple-slash reference (
import MagicString from "magic-string";
/**
* A hack to add triple-slash references to sibling d.ts files for deno.
*/
function dts() {
return {
name: "dts",
renderChunk(code, info) {
if (info.isEntry) {
const dts = "./" + info.fileName.replace(/js$/, "d.ts");
const ms = new MagicString(code);
ms.prepend(`/// <reference types="${dts}" />\n`);
code = ms.toString();
const map = ms.generateMap({hires: true});
return {code, map};
}
return code;
},
};
}
export default {
/* other rollup options */
plugins: [/* plugins */, dts()],
}; This allows Deno consumers to import your rollup build artifacts and have them be typed, without any extra effort on their part. This again assumes the d.ts files work out of box, which is not guaranteed. Between The latter technique of relying on rollup or similar is nice, but it precludes serving your modules from raw.githubusercontent.com or deno.land/x because we typically don’t check in build artifacts into source control. I found that this technique works okay when using unpkg, although I am getting weird type errors when trying to use semver redirects.
Method 1 causes typescript to produce d.ts files with module specifiers that have file extensions ( You can use the typescript transform
import typescript from "rollup-plugin-typescript2";
import {transform} from "ts-transform-import-path-rewrite";
/**
* A hack to rewrite import paths in d.ts files for deno.
*/
function transformer() {
const rewritePath = transform({
rewrite(importPath) {
// if you use method 1, you need to replace the `.ts` with `.js`
return importPath + ".js";
},
});
return {afterDeclarations: [rewritePath]};
}
export default {
/* config options */
plugins: [ts({transformers: [transformer]}), /* other plugins */],
} You can refer to the full Needless to say, anyone who is even partly responsible for this omnishambles should feel bad. I will not be turning these techniques into a library/module because I don’t want to maintain it, and in an ideal world, the TypeScript team will stop stonewalling on explicit file extensions in module specifiers, and all these techniques will be obsoleted. In that respect, I want to say good job to all the people writing constant “Any progress on this” comments in the related TypeScript issues and pinging the TypeScript maintainers directly with rambling, bad-faith arguments. With persistence we shall win. In the meantime, these techniques seem to work for smaller libraries, and you should feel free to copy-paste the code in my rollup config into your own libraries.
I think writing for node and compiling to deno is unfortunately much easier than writing for deno and compiling for node as of today (July 30th). The big difference is that node tooling is just much further advanced (bundlers, testing). I think at the end of the day, a tool which turns deno modules into real packages would be great, but I don’t think the deno ecosystem is there yet for dual publishers. |
My solution: YouTube - Integrating Deno and Node.js |
I just came across this: https://github.com/garronej/denoify It a tool to author |
I'm made another tool, inspired by denoify (that didn't meet my requirements): https://github.com/marcushultman/denoc Smaller (single dependency), more explicit but with simpler setup. |
I'm still very interested in creating authoring deno and a way to convert it to node (deno -> node). ❤️❤️❤️ |
Maybe seeing how https://github.com/ebebbington/context-finder is setup might help |
@reggi 👋 Hello, I have written a simple project in We manually keep a These are the files I need to create the npm build First, we use the build file contains the steps to make the folder and the publishing happens in the release CI. To test the node package before deploying, we set up an example node project that uses
The release is triggered via pushing a new tag that starts with |
Came across esm.sh a CDN that polyfills Node core packages with the Deno Node compatability layer and converts to ES. |
Looking through the yargs it seems they take a simpler approach without converters like Denoify. It seems like they are writing Typescript using the appropriate |
Has anyone tried to do this with WASI? |
The code to initialize the WASI context is slightly different between Node and Deno, otherwise should be fine as we're fairly compliant. |
Neat. Although Node's WASI support is still behind an experimental flag so I guess we need a couple of months/years until we can reasonably ask library users to use a WASI supporting Node version. |
It's not completely unreasonable to ask that today depending on what the library does, e.g is it impractical to do the same thing in JavaScript. WebAssembly is a lot simpler than dealing with whatever incarnation of NAPI we're at now. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions. |
Not stale I guess - I think this could be converted to a GH discussion |
I'm still a bit boggled about how little documentation there's anywhere about writing libraries for Deno, and the existence of this issue keeps reminding me about it. There's plenty about writing applications, but for libraries, the only thing I can find on deno.land is the "Add a module" button under "Third Party Modules". I just did that for |
TLDR; design pattern for single-source to CJS/ESM/TypeScript + Deno. Being somewhat frustrated by tooling in this space, and inspired by the TypeScript is used as the fundamental source language for the module source code. Importantly, all internal module imports have fully specified relative paths (with extensions). All source mode files are written in TypeScript, with '.ts' extensions. But, all internal imports use the '.js' extension. TypeScript's resolution algorithm will find the source file correctly. Yes, this is definitely confusing, but it is the officially supported method available since at least TypeScript-v2.0. The TypeScript source is transpiled into an ESM which, with some platform abstraction, can be utilized by both NodeJS and Deno. Importantly, for Deno operation, the target transpiled ESM code must be stored back into the repository (eg, in a 'dist' folder). The platform abstraction (Platform.Adapter) contains all external dependencies and platform-specific built-in variations. Platform-dependent definitions of Platform.Adapter contain the necessary implementation code. For example, from 'os-paths': // src/platform-adapters/_base.ts
export namespace Platform {
export type Adapter = {
readonly env: { readonly get: (_: string) => string | undefined };
readonly os: { readonly homedir?: () => string; readonly tmpdir?: () => string };
readonly path: {
readonly join: (..._: readonly string[]) => string;
readonly normalize: (_: string) => string;
};
readonly process: {
readonly platform: string;
};
};
} // src/platform-adapters/node.ts
import * as os from 'os';
import * as path from 'path';
import { Platform } from './_base.js';
export const adapter: Platform.Adapter = {
env: {
get: (s) => {
return process.env[s];
},
},
os,
path,
process,
}; From working TypeScript code, an Then, when the module is imported, the // src/mod.esm.ts
import { Adapt, OSPaths } from './lib/OSPaths.js';
import { adapter } from './platform-adapters/node.js';
const default_: OSPaths = Adapt(adapter).OSPaths;
export type { OSPaths };
export default default_; After creating a module, coded in TypeScript, producing ESM-compatible code (which is not simple), the platform abstraction code and Deno implementation is a small, straight-forward changeset (from xdg-app-paths). For detailed/working examples, I've just finished converting three small modules:
To be sure, I'm not overly happy with the necessary Ultimately, all of the modules are available for use by CJS/ESM/TypeScript (via NPMjs, github repo, or jsdelivr CDN) and Deno (via 'deno.land/x', github repo, or jsdelivr CDN). See the respective repositories for further implementation details and READMEs for further usage details. Of important note, avoiding CJS/TypeScript, and coding the module just to ESM and Deno would still allow the same platform-adapter pattern but with a huge drop in dev complexity, scripting, and tooling. |
@eemeli that isn’t necessarily related to this issue, but you can find all info in the manual, everything else is knowledge from Js and Ts |
@ebebbington Huh, maybe I just missed it then? I'm looking for answers to the following sorts of things, if they're in the Deno manual maybe you could give me a link?
|
There isn't a standard for creating a Deno module. JS libraries are a collection of methods, classes and objects that are made available through the user as they see fit. However well documented libraries tend to contain the following:
Import maps are not mean for libraries, but for end users. It's encouraged for library authors to use deps.ts convention or a similar approach |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Let’s say that I have a library which is published on npm. If that library
what is the best way to add support for deno? Is there a good example repository which does this?
Additionally, if I have library which depends only on modules which dual support both node and deno, does this story change?
Related: #2644
The text was updated successfully, but these errors were encountered: