@@ -768,3 +768,275 @@ let output_result = transform(TransformOptions {
768768 specifier_mappings: None,
769769}).await?;
770770` ` `
771+
772+ # # Publishing Your Deno Project as a Monorepo using dnt
773+
774+ > Before providing theoretical guidance, let's look at how to achieve this in
775+ > practice. After completion, I will explain the advantages of this project
776+ > management solution.
777+
778+ # ## Tools
779+
780+ 1. [deno](https://deno.com/)
781+ 2. [pnpm](https://pnpm.io/installation)
782+
783+ # ## Preparation
784+
785+ 1. Create your project :
786+ ` ` ` shell
787+ deno init dnt-mono
788+ # cd dnt-mono
789+ # code . # open in ide
790+ ` ` `
791+ 2. Initialize a git repository
792+ ` ` ` shell
793+ git init
794+ echo "npm\n node_modules" > .gitignore # ignore the npm folder
795+ ` ` `
796+ 3. Initialize package.json, and other files typically required by npm/pnpm
797+ ` ` ` shell
798+ npm init --yes --private # create a package.json file
799+ echo "MIT" > LICENSE
800+ echo "# Hello Dnt ❤️ Monorepo" > README.md
801+ echo "packages:\n - \" npm/*\" " > pnpm-workspace.yaml
802+ ` ` `
803+ 4. Prepare the dnt script
804+
805+ ` ` ` shell
806+ deno add @deno/dnt
807+ ` ` `
808+
809+ Refer to [Setup](https://github.com/denoland/dnt?tab=readme-ov-file#setup),
810+ as we need to build multiple npm packages, create the `scripts/npmBuilder.ts`
811+ file :
812+
813+ ` ` ` ts
814+ import { build, BuildOptions, emptyDir } from "@deno/dnt";
815+ import fs from "node:fs";
816+ import { fileURLToPath, pathToFileURL } from "node:url";
817+
818+ const rootDir = import.meta.resolve("../");
819+ const rootResolve = (path: string) => fileURLToPath(new URL(path, rootDir));
820+ export const npmBuilder = async (config: {
821+ packageDir: string;
822+ version?: string;
823+ importMap?: string;
824+ options?: Partial<BuildOptions>;
825+ }) => {
826+ const { packageDir, version, importMap, options } = config;
827+ const packageResolve = (path: string) =>
828+ fileURLToPath(new URL(path, packageDir));
829+ const packageJson = JSON.parse(
830+ fs.readFileSync(packageResolve("./package.json"), "utf-8"),
831+ );
832+ // remove some field which dnt will create. if you known how dnt work, you can keep them.
833+ delete packageJson.main;
834+ delete packageJson.module;
835+ delete packageJson.exports;
836+
837+ console.log(` \nstart dnt: ${packageJson.name}`);
838+
839+ const npmDir = pathToFileURL(
840+ rootResolve(`./npm/${packageJson.name.split("/").pop()}`),
841+ ).href;
842+ const npmResolve = (path : string) => fileURLToPath(new URL(path, npmDir));
843+
844+ await emptyDir(npmDir);
845+
846+ if (version) {
847+ Object.assign(packageJson, { version : version });
848+ }
849+
850+ await build({
851+ entryPoints : [{ name: ".", path: packageResolve("./index.ts") }],
852+ outDir : npmDir,
853+ packageManager : " pnpm" ,
854+ shims : {
855+ deno : true,
856+ },
857+ // you should open it in actual
858+ test : false,
859+ importMap : importMap,
860+ package : packageJson,
861+ // custom by yourself
862+ compilerOptions : {
863+ lib : ["DOM", "ES2022"],
864+ target : " ES2022" ,
865+ emitDecoratorMetadata : true,
866+ },
867+ postBuild() {
868+ // steps to run after building and before running the tests
869+ Deno.copyFileSync(rootResolve("./LICENSE"), npmResolve("./LICENSE"));
870+ Deno.copyFileSync(
871+ packageResolve("./README.md"),
872+ npmResolve("./README.md"),
873+ );
874+ },
875+ ...options,
876+ });
877+ };
878+ ```
879+
880+ ### Main Steps
881+
882+ 1 . Create two subfolders and add some project files
883+
884+ ``` shell
885+ # start from root
886+ mkdir packages/module-a
887+ cd packages/module-a
888+ echo " export const a = 1;" > index.ts
889+ echo " # @dnt-mono/module-a" > README.md
890+ npm init --scope @dnt-mono --yes # name: @dnt-mono/module-a
891+ ```
892+
893+ Repeat the steps to create a ` module-b ` folder
894+
895+ ``` shell
896+ # start from root
897+ mkdir packages/module-b
898+ cd packages/module-b
899+ echo " import { a } from \" @dnt-mono/module-a\" ;\nexport const b = a + 1;" > index.ts
900+ echo " # @dnt-mono/module-b" > README.md
901+ npm init --scope @dnt-mono --yes # name: @dnt-mono/module-b
902+
903+ pnpm add @dnt-mono/module-a --workspace # add module-a as a dependency
904+ ```
905+
906+ 2 . In this example, ` module-b ` depends on ` module-a ` , and we used the specifier
907+ ` @dnt-mono/module-a ` in the code, so we need some configurations to make the
908+ deno language server work correctly. In the ` imports ` field of ` deno.json ` ,
909+ add these configurations:
910+
911+ ``` jsonc
912+ " @dnt-mono/module-a" : " ./packages/module-a/index.ts" , // in imports
913+ " @dnt-mono/module-b" : " ./packages/module-b/index.ts" // in imports
914+ ```
915+
916+ 3 . Next, create the build script and configuration files
917+
918+ 1 . ` scripts/build_npm.ts `
919+
920+ ``` ts
921+ import { npmBuilder } from " ./npmBuilder.ts" ;
922+
923+ const version = Deno .args [0 ];
924+ await npmBuilder ({
925+ packageDir: import .meta .resolve (" ../packages/module-a/" ),
926+ importMap: import .meta .resolve (" ./import_map.npm.json" ),
927+ version ,
928+ });
929+ await npmBuilder ({
930+ packageDir: import .meta .resolve (" ../packages/module-b/" ),
931+ importMap: import .meta .resolve (" ./import_map.npm.json" ),
932+ version ,
933+ });
934+ ```
935+
936+ 2. ` scripts/import_map.npm.json `
937+
938+ ` ` ` json
939+ {
940+ "imports": {
941+ "@dnt-mono/module-a": "npm:@dnt-mono/module-a",
942+ "@dnt-mono/module-b": "npm:@dnt-mono/module-b"
943+ }
944+ }
945+ ` ` `
946+
947+ 4. Then , in your ` deno.json ` , configure the build command :
948+
949+ ` ` ` jsonc
950+ "build": "deno run -A ./scripts/build_npm.ts" // in tasks
951+ ` ` `
952+
953+ 5. Finally , try executing the build command to create the npm directory
954+ ` ` ` shell
955+ deno task build
956+ ` ` `
957+ Now , you should see the npm directory has been populated with the module - a
958+ and module - b folders ready for npm publishing . You can try to publish these
959+ npm packages :
960+ ` ` ` shell
961+ pnpm publish -r --no-git-checks --dry-run # you should remove --dry-run for an actual run
962+ ` ` `
963+
964+ ### How It Works
965+
966+ 1. We use deno as the language server , which is quite powerful , vastly improved
967+ from tsc itself through customized development .
968+ 2. So here , the package .json is just a " template file" and not a configuration
969+ file . The only configuration file that goes into effect during development is
970+ deno .json .
971+ 3. Hence , pnpm is just a tool for the final output built by dnt , meaning it only
972+ serves the ` npm/* ` directory . This is also why ` pnpm-workspaces.yaml ` is
973+ configured as it is .
974+ 4. The ` import_map.npm.json ` used in dnt is essential . We can ' t use `deno.json`
975+ directly as ` importMap ` because ` deno.json ` is configured for the deno
976+ language server , while ` import_map.npm.json ` is for dnt / pnpm use . In complex
977+ projects , it ' s advisable to manage it automatically with a script.
978+
979+ ### Advanced Tips
980+
981+ In deno development , our philosophy is file - oriented rather than
982+ module - oriented . Therefore , if needed , you may want to add this kind of
983+ configuration in ` deno.json ` :
984+
985+ ` ` ` jsonc
986+ {
987+ // ...
988+ "imports": {
989+ // ...
990+ "@dnt-mono/module-a": "./packages/module-a/index.ts",
991+ "@dnt-mono/module-a/": "./packages/module-a/src/",
992+ "@dnt-mono/module-b": "./packages/module-b/index.ts",
993+ "@dnt-mono/module-b/": "./packages/module-b/src/"
994+ // ...
995+ }
996+ }
997+ ` ` `
998+
999+ I prefer to put files other than ` index.ts ` into a ` src ` directory , which aligns
1000+ more with the style of node projects .
1001+
1002+ > However , remember not to move the ` index.ts ` file to the ` src ` directory as
1003+ > well , as it could cause exceptions
1004+ > [#249 ](https :// github.com/denoland/dnt/issues/249).
1005+
1006+ Then , it ' s about the dnt configuration, where you need to iterate over all your
1007+ files and configure them in the entryPoints :
1008+
1009+ ` ` ` ts
1010+ build({
1011+ entryPoints: [
1012+ // default entry
1013+ { name: ".", path: packageResolve("./index.ts") },
1014+ // src files
1015+ ALL_SRC_TS_FILES.map((name) => ({
1016+ name: ` ./ $ {name }` ,
1017+ path: ` ./ src / $ {name }` ,
1018+ })),
1019+ ],
1020+ // ...
1021+ });
1022+ ` ` `
1023+
1024+ Now , you can write code like this :
1025+
1026+ ` ` ` ts
1027+ import { xxx } from "@dnt-mono/module-a/xxx.ts";
1028+ ` ` `
1029+
1030+ ### Points to Note
1031+
1032+ 1. Plan your project structure well to avoid cyclic dependencies . If needed , you
1033+ should configure peerDependencies yourself .
1034+ 2. Don ' t self-import within a module.
1035+ > The language server doesn ' t understand that you intend to publish to npm,
1036+ > so even if deno works correctly , your goal is to make it work with node as
1037+ > well .
1038+ ` ` ` ts
1039+ import { a } from "@dnt-mono/module-a"; // don't import module-a in module-a
1040+ ` ` `
1041+ It is advisable to write lint rules to avoid these mistakes in actual
1042+ projects .
0 commit comments