@@ -768,3 +768,273 @@ 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 theme.
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+ importMap : importMap,
858+ package : packageJson,
859+ // custom by yourself
860+ compilerOptions : {
861+ lib : ["DOM", "ES2022"],
862+ target : " ES2022" ,
863+ emitDecoratorMetadata : true,
864+ },
865+ postBuild() {
866+ // steps to run after building and before running the tests
867+ Deno.copyFileSync(rootResolve("./LICENSE"), npmResolve("./LICENSE"));
868+ Deno.copyFileSync(
869+ packageResolve("./README.md"),
870+ npmResolve("./README.md"),
871+ );
872+ },
873+ ...options,
874+ });
875+ };
876+ ```
877+
878+ ### Main Steps
879+
880+ 1 . Create two subfolders and add some project files
881+
882+ ``` shell
883+ # start from root
884+ mkdir packages/module-a
885+ cd packages/module-a
886+ echo " export const a = 1;" > index.ts
887+ echo " # @dnt-mono/module-a" > README.md
888+ npm init --scope @dnt-mono --yes # name: @dnt-mono/module-a
889+ ```
890+
891+ Repeat the steps to create a ` module-b ` folder
892+
893+ ``` shell
894+ # start from root
895+ mkdir packages/module-b
896+ cd packages/module-b
897+ echo " import { a } from \" @dnt-mono/module-a\" ;\nexport const b = a + 1;" > index.ts
898+ echo " # @dnt-mono/module-b" > README.md
899+ npm init --scope @dnt-mono --yes # name: @dnt-mono/module-b
900+
901+ pnpm add @dnt-mono/module-a --workspace # add module-a as a dependency
902+ ```
903+
904+ 2 . In this example, ` module-b ` depends on ` module-a ` , and we used the specifier
905+ ` @dnt-mono/module-a ` in the code, so we need some configurations to make the
906+ deno language server work correctly. In the ` imports ` field of ` deno.json ` ,
907+ add these configurations:
908+
909+ ``` jsonc
910+ " @dnt-mono/module-a" : " ./packages/module-a/index.ts" , // in imports
911+ " @dnt-mono/module-b" : " ./packages/module-b/index.ts" // in imports
912+ ```
913+
914+ 3 . Next, create the build script and configuration files
915+
916+ 1 . ` scripts/build_npm.ts `
917+
918+ ``` ts
919+ import { npmBuilder } from " ./npmBuilder.ts" ;
920+
921+ const version = Deno .args [0 ];
922+ await npmBuilder ({
923+ packageDir: import .meta .resolve (" ../packages/module-a/" ),
924+ importMap: import .meta .resolve (" ./import_map.npm.json" ),
925+ version ,
926+ });
927+ await npmBuilder ({
928+ packageDir: import .meta .resolve (" ../packages/module-b/" ),
929+ importMap: import .meta .resolve (" ./import_map.npm.json" ),
930+ version ,
931+ });
932+ ```
933+
934+ 2. ` scripts/import_map.npm.json `
935+
936+ ` ` ` json
937+ {
938+ "imports": {
939+ "@dnt-mono/module-a": "npm:@dnt-mono/module-a",
940+ "@dnt-mono/module-b": "npm:@dnt-mono/module-b"
941+ }
942+ }
943+ ` ` `
944+
945+ 4. Then , in your ` deno.json ` , configure the build command :
946+
947+ ` ` ` jsonc
948+ "build": "deno run -A ./scripts/build_npm.ts" // in tasks
949+ ` ` `
950+
951+ 5. Finally , try executing the build command to create the npm directory
952+ ` ` ` shell
953+ deno task build
954+ ` ` `
955+ Now , you should see the npm directory has been populated with the module - a
956+ and module - b folders ready for npm publishing . You can try to publish these
957+ npm packages :
958+ ` ` ` shell
959+ pnpm publish -r --no-git-checks --dry-run # you should remove --dry-run for an actual run
960+ ` ` `
961+
962+ ### How It Works
963+
964+ 1. We use deno as the language server , which is quite powerful , vastly improved
965+ from tsc itself through customized development .
966+ 2. So here , the package .json is just a " template file" and not a configuration
967+ file . The only configuration file that goes into effect during development is
968+ deno .json .
969+ 3. Hence , pnpm is just a tool for the final output built by dnt , meaning it only
970+ serves the ` npm/* ` directory . This is also why ` pnpm-workspaces.yaml ` is
971+ configured as it is .
972+ 4. The ` import_map.npm.json ` used in dnt is essential . We can ' t use `deno.json`
973+ directly as ` importMap ` because ` deno.json ` is configured for the deno
974+ language server , while ` import_map.npm.json ` is for dnt / pnpm use . In complex
975+ projects , it ' s advisable to manage it automatically with a script.
976+
977+ ### Advanced Tips
978+
979+ In deno development , our philosophy is file - oriented rather than
980+ module - oriented . Therefore , if needed , you may want to add this kind of
981+ configuration in ` deno.json ` :
982+
983+ ` ` ` jsonc
984+ {
985+ // ...
986+ "imports": {
987+ // ...
988+ "@dnt-mono/module-a": "./packages/module-a/index.ts",
989+ "@dnt-mono/module-a/": "./packages/module-a/src/",
990+ "@dnt-mono/module-b": "./packages/module-b/index.ts",
991+ "@dnt-mono/module-b/": "./packages/module-b/src/"
992+ // ...
993+ }
994+ }
995+ ` ` `
996+
997+ I prefer to put files other than ` index.ts ` into a ` src ` directory , which aligns
998+ more with the style of node projects .
999+
1000+ > However , remember not to move the ` index.ts ` file to the ` src ` directory as
1001+ > well , as it could cause exceptions
1002+ > [#249 ](https :// github.com/denoland/dnt/issues/249).
1003+
1004+ Then , it ' s about the dnt configuration, where you need to iterate over all your
1005+ files and configure them in the entryPoints :
1006+
1007+ ` ` ` ts
1008+ build({
1009+ entryPoints: [
1010+ // default entry
1011+ { name: ".", path: packageResolve("./index.ts") },
1012+ // src files
1013+ ALL_SRC_TS_FILES.map((name) => ({
1014+ name: ` ./ $ {name }` ,
1015+ path: ` ./ src / $ {name }` ,
1016+ })),
1017+ ],
1018+ // ...
1019+ });
1020+ ` ` `
1021+
1022+ Now , you can write code like this :
1023+
1024+ ` ` ` ts
1025+ import { xxx } from "@dnt-mono/module-a/xxx.ts";
1026+ ` ` `
1027+
1028+ ### Points to Note
1029+
1030+ 1. Plan your project structure well to avoid cyclic dependencies . If needed , you
1031+ should configure peerDependencies yourself .
1032+ 2. Don ' t self-import within a module.
1033+ > The language server doesn ' t understand that you intend to publish to npm,
1034+ > so even if deno works correctly , your goal is to make it work with node as
1035+ > well .
1036+ ` ` ` ts
1037+ import { a } from "@dnt-mono/module-a"; // don't import module-a in module-a
1038+ ` ` `
1039+ It is advisable to write lint rules to avoid these mistakes in actual
1040+ projects .
0 commit comments