Skip to content
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

Add first class Javascript/Typescript support to the Mill build tool #4293

Merged
merged 12 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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("[email protected]")
}

}

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
Loading