Skip to content

Commit

Permalink
Add first class Javascript/Typescript support to the Mill build tool (#…
Browse files Browse the repository at this point in the history
…4293)

This pr implements the examples for jslib/dependencies.

#3927

Checklist:
- [x] **example/jslib/publish**
     - [x]  1-publish
     - [x]  2-realistic
     
Key changes:
- Packages can now be imported without the node_modules prefix.
- PublishMeta - package.json (for publish) generation
- Prepare js code for publishing in publish directory
  • Loading branch information
monyedavid authored Jan 16, 2025
1 parent b2111f4 commit e1f43c5
Show file tree
Hide file tree
Showing 47 changed files with 986 additions and 77 deletions.
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
*** xref:javascriptlib/dependencies.adoc[]
*** xref:javascriptlib/module-config.adoc[]
*** xref:javascriptlib/testing.adoc[]
*** xref:javascriptlib/publishing.adoc[]
* xref:comparisons/why-mill.adoc[]
** xref:comparisons/maven.adoc[]
** xref:comparisons/gradle.adoc[]
Expand Down
14 changes: 14 additions & 0 deletions docs/modules/ROOT/pages/javascriptlib/publishing.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
= Typescript Packaging & Publishing
:page-aliases: Publishing_Typescript_Projects.adoc

include::partial$gtag-config.adoc[]

This page will discuss common topics around publishing your Typescript projects for others to use.

== Simple publish

include::partial$example/javascriptlib/publishing/1-publish.adoc[]

== Realistic publish

include::partial$example/javascriptlib/publishing/2-realistic.adoc[]
2 changes: 1 addition & 1 deletion example/javascriptlib/basic/1-simple/foo/src/foo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Map} from 'node_modules/immutable';
import {Map} from 'immutable';

interface User {
firstName: string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {generateUser, defaultRoles} from "foo/foo";
import {Map} from 'node_modules/immutable';
import {Map} from 'immutable';

// Define the type roles object
type RoleKeys = "admin" | "user";
Expand Down
3 changes: 1 addition & 2 deletions example/javascriptlib/basic/1-simple/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {pathsToModuleNameMapper} from 'ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {pathsToModuleNameMapper} from 'ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Map} from 'node_modules/immutable';
import {Map} from 'immutable';

const defaultRoles: Map<string, string> = Map({ prof: "Professor" });
export default defaultRoles
3 changes: 1 addition & 2 deletions example/javascriptlib/basic/4-multi-modules/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {pathsToModuleNameMapper} from 'ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {pathsToModuleNameMapper} from 'ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {pathsToModuleNameMapper} from 'ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {sortBy} from 'node_modules/lodash'
import {sortBy} from 'lodash'

const args = process.argv.slice(2);
console.log(`Sorted with lodash: [${sortBy(args).join(",")}]`);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {sortBy} from 'node_modules/lodash'
import {sortBy} from 'lodash'

const args = process.argv.slice(2);
console.log(`Sorted with lodash: [${sortBy(args).join(",")}]`);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {sortBy} from 'node_modules/lodash'
import {sortBy} from 'lodash'

const args = process.argv.slice(2);
console.log(`Sorted with lodash: [${sortBy(args).join(",")}]`);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import {sortBy} from 'node_modules/lodash';
const PackageLock = require.resolve(`package-lock.json`);
import {sortBy} from 'lodash';
const PackageLock = require.resolve(`../../package-lock.json`);

const args = process.argv.slice(2);
console.log(`Sorted with lodash: [${sortBy(args).join(",")}]`);
Expand Down
2 changes: 1 addition & 1 deletion example/javascriptlib/module/2-custom-tasks/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,5 @@ object foo extends TypeScriptModule {
> mill foo.run "Hello World!"
Bar.value: 123
text: Hello World!
Line count: 13
Line count: 9
*/
1 change: 0 additions & 1 deletion example/javascriptlib/module/3-override-tasks/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ object foo extends TypeScriptModule {

def compile = Task {
println("Compiling...")
os.copy(sources().path, super.compile()._2.path, mergeFolders = true)
super.compile()
}

Expand Down
3 changes: 1 addition & 2 deletions example/javascriptlib/module/5-resources/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'node_modules/ts-jest';
import {pathsToModuleNameMapper} from 'ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
Expand Down
2 changes: 2 additions & 0 deletions example/javascriptlib/publishing/1-publish/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
registry=https://registry.npmjs.org
//registry.npmjs.org/:_authToken=...
1 change: 1 addition & 0 deletions example/javascriptlib/publishing/1-publish/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Greet!
63 changes: 63 additions & 0 deletions example/javascriptlib/publishing/1-publish/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package build

import mill._, javascriptlib._

object foo extends PublishModule {
def publishMeta = Task {
PublishMeta(
name = "mill-simple",
version = "1.0.0",
description = "A simple Node.js command-line tool",
files = Seq("README.md"),
bin = Map(
"greet" -> "src/foo.js"
)
)
}
}

// You'll need to define some metadata in the `publishMeta` tasks.
// This metadata is roughly equivalent to what you'd define in a
// https://docs.npmjs.com/cli/v11/configuring-npm/package-json[`package.json` file].

// Important `package.json` info required for publishing are auto-magically generated.
// `main` file is by default the file defined in the `mainFileName` task, it can be modified if needed.

// Use the `.npmrc` file to include authentication tokens for publishing to npm or
// change the regsitry to publish to a private registry.

// The package.json generated for this simple publish:

//// SNIPPET:BUILD
// [source,json]
// ----
//{
// "name": "mill-simple",
// "version": "1.0.0",
// "description": "A simple Node.js command-line tool",
// "license": "MIT",
// "main": "dist/src/foo.js",
// "types": "declarations/src/foo.d.ts",
// "files": ["README.md", "dist", "declarations"],
// "bin": {
// "greet": "./dist/src/foo.js"
// },
// "exports": {
// ".": "./dist/src/foo.js"
// },
// "typesVersions": {
// "*": {
// "./dist/src/foo": ["declarations/src/foo.d.ts"]
// }
// }
//}
// ----
//// SNIPPET:END

/** Usage
> npm i -g mill-simple # install the executable file globally
...

> greet "James Bond"
Hello James Bond!
*/
11 changes: 11 additions & 0 deletions example/javascriptlib/publishing/1-publish/foo/src/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env node

export class Foo {
static hello() {
const args = process.argv.slice(2);
const name = args[0] || 'unknown';
return `Hello ${name}!`
}
}

console.log(Foo.hello());
2 changes: 2 additions & 0 deletions example/javascriptlib/publishing/2-realistic/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
registry=https://registry.npmjs.org
//registry.npmjs.org/:_authToken=...
1 change: 1 addition & 0 deletions example/javascriptlib/publishing/2-realistic/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Mill - advance publish module
99 changes: 99 additions & 0 deletions example/javascriptlib/publishing/2-realistic/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package build

import mill._, javascriptlib._

object foo extends TypeScriptModule {
object bar extends TypeScriptModule {
def npmDeps = Seq("immutable@4.3.7")
}

}

object qux extends PublishModule {
def moduleDeps = Seq(foo, foo.bar)

def generatedSources = Task {
os.write(
Task.dest / "qux.generated.ts",
s"""export default class QuxGen {
| static value: number = 123
|}
""".stripMargin
)

Seq(PathRef(Task.dest))
}

def exports = Map(
"./qux/generate_user" -> "src/generate_user.js",
"./foo" -> "foo/src/foo.js",
"./foo/bar" -> "foo/bar/src/bar.js"
)

def publishMeta = PublishMeta(
name = "mill-realistic",
version = "1.0.3",
description = "A simple Node.js command-line tool",
files = Seq("README.md"),
bin = Map(
"qux" -> "src/qux.js"
)
)

object test extends TypeScriptTests with TestModule.Jest
}

// In this example, we define multiple exports for our application with the `export` task
// The package.json generated for this lib publish:

//// SNIPPET:BUILD
// [source,json]
// ----
// {
// "name": "mill-realistic",
// "version": "1.0.3",
// "description": "A simple Node.js command-line tool",
// "license": "MIT",
// "main": "dist/src/qux.js",
// "types": "declarations/src/qux.d.ts",
// "files": [
// "README.md",
// "dist",
// "declarations"
// ],
// "bin": {
// "qux": "./dist/src/qux.js"
// },
// "dependencies": {
// "immutable": "4.3.7"
// },
// "devDependencies": {
// "immutable": "4.3.7"
// },
// "exports": {
// ".": "./dist/src/qux.js",
// "./qux/generate_user": "./dist/src/generate_user.js",
// "./foo": "./dist/foo/src/foo.js",
// "./foo/bar": "./dist/foo/bar/src/bar.js"
// },
// "typesVersions": {
// "*": { ... }
// }
//}
// ----
//// SNIPPET:END

/** Usage
> mill qux.test
PASS .../qux.test.ts
...

> npm i -g mill-realistic
...

> qux James Bond prof
{ prof: 'Professor' }
prof
Professor
Hello James Bond Professor
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
From Foo/Bar
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {Map} from 'immutable';

const defaultRoles: Map<string, string> = Map({ prof: "Professor" });
export default defaultRoles
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
From Foo
5 changes: 5 additions & 0 deletions example/javascriptlib/publishing/2-realistic/foo/src/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default interface User {
firstName: string
lastName: string
role: string
}
32 changes: 32 additions & 0 deletions example/javascriptlib/publishing/2-realistic/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// @ts-nocheck
import {pathsToModuleNameMapper} from 'ts-jest';
import {compilerOptions} from './tsconfig.json'; // this is a generated file.

// Remove unwanted keys
const moduleDeps = {...compilerOptions.paths};
delete moduleDeps['*'];
delete moduleDeps['typeRoots'];

// moduleNameMapper evaluates in order they appear,
// sortedModuleDeps makes sure more specific path mappings always appear first
const sortedModuleDeps = Object.keys(moduleDeps)
.sort((a, b) => b.length - a.length) // Sort by descending length
.reduce((acc, key) => {
acc[key] = moduleDeps[key];
return acc;
}, {});

export default {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: [
'<rootDir>/**/**/**/*.test.ts',
'<rootDir>/**/**/**/*.test.js',
],
transform: {
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
'^.+\\.(js|jsx)$': 'babel-jest', // Use babel-jest for JS/JSX files
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: pathsToModuleNameMapper(sortedModuleDeps) // use absolute paths generated in tsconfig.
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
From Qux
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import User from "foo/foo";
import DefaultRoles from "foo/bar/bar";

/**
* Generate a user object based on command-line arguments
* @param args Command-line arguments
* @returns User object
*/
export function generateUser(args: string[]): User {
return {
firstName: args[0] || "unknown", // Default to "unknown" if first-name not found
lastName: args[1] || "unknown", // Default to "unknown" if last-name not found
role: DefaultRoles.get(args[2], ""), // Default to empty string if role not found
};
}
Loading

0 comments on commit e1f43c5

Please sign in to comment.