npm - Bør jeg ekskludere avhengigheter for et modulært Typescript-prosjekt?


Jeg prøver for øyeblikket å lage en NPM-pakke for et Typescript-prosjekt (jeg bygger ved å bruke gulp og browserify). Problemet er at pakken som skal brukes for øyeblikket ikke bruker moduler, så jeg prøver å pakke en frittstående bundle ved hjelp av Browserify.

Først og fremst, kan det være et problem å pakke alle avhengighetene sammen? Så vidt jeg kan se, pakker den bundne js-filen rett og slett inn avhengighetene mine (three og hammerjs) i det globale navnerommet. Min pakkekonsument har en annen komponent som inkluderer hammerjs (nesten samme versjon), så jeg mistenker at komponenten som ble inkludert sist, vil definere hvilken hammerjs-pakke som er tilgjengelig for applikasjonen min? Hvordan håndterer andre NPM-pakker som fungerer selvstendig dette?

Jeg fant ut at Browserify enkelt kunne ekskludere sine avhengigheter ved å sette bundleExternal til false eller ved å ekskludere avhengigheter én etter én og deretter inkludere de bibliotekene i nettleseren. Dette fungerer ikke, og jeg får en "Cannot find module 'hammerjs'"-feil i konsollen. Jeg fant ut hvordan man bruker exclude i Browserify, og hvordan man browserify alle avhengigheter separat, noe som også fungerte, men så vidt jeg kan se ville dette være det samme som å bare inkludere dem direkte, siden jeg ikke kan bare inkludere hammer.min.js-filen fra deres nettside.

Kort fortalt

Hva er den riktige måten å pakke en modulær Typescript NPM-pakke på og håndtere avhengigheter for bruk i en applikasjon som ikke støtter moduler?

Svar #1

For å bygge en fungerende npm-pakke må du levere modulen din på ruten som er spesifisert i package.json som main. Vanligvis bør dette være inne i dist/my-main-class.js.

Så inne i min src har jeg en klasse/modul/romnavn som representerer prosjektet mitt:

class MyMainClass{
 static myMethod = function(){
    console.log('hello');
 }
}

Og på slutten av filen har jeg dette: export=MyMainClass;.

Hvis jeg publiserer pakken min med navnet MyMainClass: $ npm publish MyMainClass, kan brukerne mine importere den bare med:

let MyMainClass = require('MyMainClass');
MyMainClass.myMethod();

Ikke glem å bygge før du publiserer. Slik sørget jeg for at jeg aldri glemmer:

"build": "tsc -p .",
"prepublish": "npm run build",

Hvis du vil minifisere koden din eller gjøre noe annet, kan du bruke gulp/webpack før publisering og rette deg mot dist-mappen din.

"minify": "gulp/grunt/webpack minify dist/**.js",
"build": "tsc -p .",
"prepublish": "npm run build && npm run minify",

Svar #2

Jeg er fortsatt ikke helt sikker på den beste måten å håndtere avhengigheter på, men veien jeg valgte å gå var å lage en mylib.js og en mylib.min.js som distribueres i npm-pakken og som ikke inneholder noen avhengigheter. Ved siden av de distribuerte js-filene inkluderte jeg biblioteket i modulær form som kan brukes med for eksempel browserify. Problemet jeg hadde med browserify var at output-en, der jeg prøvde å ekskludere biblioteker, fortsatt var avhengig av en form for require; da jeg prøvde webpack fungerte det rett ut av boksen.

Jeg inkluderte de komplette byggeskriptene som referanse.

Filstruktur

├───dist                // Output folder for all my distributable standalone js files
│   ├───mylib.d.ts      // Manually writtes declaration file
│   ├───myLib.js        // Distributable without dependencies
│   └───myLib.min.js    // Compressed distributable without dependencies
├───lib
│   ├───myLib.js        // Compiled src
│   └───myLib.d.ts      // Compiled d.ts 
├───src                 // Folder containing my Typescript src
├───tests               // Output folder for my tests
└───testSrc             // Src folder for my test code
    ├───test.html
    └───unittests

package.json

{
  "name": "mylib",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "build": "gulp compile && gulp webpack",
    "prepublish": "gulp prepublish"
  },
  "main": "lib/mylib.js",
  "typings": "lib/mylib",
  "dependencies": {
    "@types/es6-promise": "0.0.32",
    "@types/hammerjs": "2.0.33",
    "@types/three": "0.0.24",
    "es6-promise": "4.0.5",
    "hammerjs": "2.0.8",
    "three": "0.82.1"
  },
  "devDependencies": {
    "@types/jasmine": "2.5.37",
    "gulp": "3.9.1",
    "gulp-cli": "1.2.2",
    "gulp-concat": "2.6.0",
    "gulp-copy": "0.0.2",
    "gulp-jasmine": "2.4.2",
    "gulp-preprocess": "2.0.0",
    "gulp-typescript": "3.1.2",
    "jasmine": "2.5.2",
    "ts-loader": "1.0.0",
    "typescript": "2.0.6",
    "webpack-stream": "3.2.0"
  }
}

Gulpfile.js

var gulp = require('gulp');
var ts = require('gulp-typescript');

// Create projects from tsconfig.json
var tsProject = ts.createProject('tsconfig.json');
var mainTestTsProject = ts.createProject('testSrc/tsconfig.json');
var jasmineTsProject = ts.createProject('testSrc/unittests/tsconfig.json');

// External build libraries
var jasmine = require("gulp-jasmine");
var concat = require("gulp-concat");
var copy = require("gulp-copy");
var preprocess = require("gulp-preprocess");
var webpack = require("webpack-stream");

// Compile the modular library
gulp.task('compile', function () {
    return tsProject.src()
        .pipe(tsProject())
        .pipe(gulp.dest("lib"));
});

// Pack and distribute
gulp.task('webpack', function (callback) {
    var config = require("./webpack.config.js");
    return tsProject.src()
        .pipe(webpack(config))
        .pipe(gulp.dest('./dist'))
});

// Pre-process the test.html
gulp.task('preprocessMainHtml', function () {
    return gulp.src('./testSrc/*.html')
        .pipe(preprocess({ context: { CURRENT_TIMESTAMP: Date.now() } }))
        .pipe(gulp.dest('./tests/'));
});

// Copy output libraries for testing
gulp.task('copyLibs', function () {
    return gulp.src(['./dist/*.js', './dist/*.map'])
        .pipe(copy('./tests', { prefix: 1 }));
});

// Compile the test-html main javascript file
gulp.task('compileMainTest', ['copyLibs', 'preprocessMainHtml'], function (callback) {
    return mainTestTsProject.src()
        .pipe(mainTestTsProject())
        .pipe(concat('mylib-test.js'))
        .pipe(gulp.dest("tests"));
});

gulp.task('prepublish', ['compile', 'webpack']);

gulp.task('test', ['compile'], function () {
    return jasmineTsProject.src()
        .pipe(jasmineTsProject())
        .pipe(gulp.dest("tests/unittests"))
        .pipe(jasmine());
});

gulp.task("default", ['webpack', 'compileMainTest']);

webpack.config.js

module.exports = {
    resolve: {
        extensions: ['', '.js', '.ts', '.tsx']
    },
    module: {
        loaders: [
            { test: /\.tsx?$/, loader: 'ts' },
        ]
    },
    externals: {
        "hammerjs": "Hammer",
        "three": "THREE"
    },
    entry: {
        "MyLib": ['./src/mylib.ts'],
    },
    output: {
        path: __dirname + '/dist',
        filename: 'mylib.js',
        libraryTarget: "umd",
        library: 'MyLib'
    },
    devtool: 'source-map',
    debug: true
}

Manuelt skrevet deklarasjonsfil

// Manually maintained declaration file

// External references
/// <reference types="hammerjs" />
/// <reference types="three" />

// This namespace should map to what is exported in the Gruntfile.js
export as namespace MyLib;

export declare class MyMainClass {
    constructor(a: string);
}

Js src i test.html

<!-- All mylib.js dependencies -->
<script src="../node_modules/three/build/three.js?_=<!-- @echo CURRENT_TIMESTAMP -->"></script>
<script src="../node_modules/hammerjs/hammer.js?_=<!-- @echo CURRENT_TIMESTAMP -->"></script>

<!-- mylib.js library -->
<script src="./mylib.js?_=<!-- @echo CURRENT_TIMESTAMP -->"></script>

<!-- mylib.js test source -->
<script src="./mylib-test.js?_=<!-- @echo CURRENT_TIMESTAMP -->"></script>