Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get webpack to include the generated declaration files for a Typescript Nodejs (not browser) project (and how to consume)

Tags:

I have 2 typescript projects, 1 depends on the other. I can generate the typescript declaration files by setting "declaration": true in tsconfig (which would be applicable to both projects). I also set "outDir" to "./dist". I can see plenty of generated declaration files emitted to the dist folder.

I am also using webpack. But how do I get wepack to include all those declaration files in the bundle so that the client can consume it?

So my tsconfig looks like this:

{
  "compilerOptions": {
    "allowJs": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "module": "commonjs",
    "moduleResolution": "Node",
    "noImplicitAny": true,
    "sourceMap": true,
    "strictNullChecks": true,
    "target": "es5",
    "declaration": true,
    "outDir": "./dist",
    "types": [
      "mocha", "node", "./dist/index.d.ts"
    ],
    "lib": [
      "es5",
      "es2015",
      "es6",
      "dom"
    ]
  },
  "include": [
    "lib/**/*", "tests/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

However, this is not working. The see the following error message:

ERROR in /Users/Plastikfan/dev/github/js/jaxom/tsconfig.json
[tsl] ERROR
      TS2688: Cannot find type definition file for './dist/index.d.ts'.

Actually, when I look at this:

"types": [
  "mocha", "node", "./dist/index.d.ts"
],

this makes me concerned, because /dist/index.d.ts is an output of the webpack/typescript build process, so how can the build see its content at the beginning of the build process when presumably it would not exist. To me this doesnt seem right, so how do I declare that "./dist/index.d.ts" are the declarations for the project?

Then once this is fixed, how does the typescript client consume the typescript/javascript package; ie I have another project (zenobia) also a typescript project, which needs to consume the typescript project from npm, so what do I need to do to get this to work properly? Jaxom is the source typescript project to be consumed and in it's index file, its exporting all the client facing declarations actually it looks like this:

index.ts:
export * from './types';
export * from './specService/spec-option-service.class';
export * from './converter/xpath-converter.class';

which is the same as the corresponding generated dist/index.d.ts

On the cosuming side (zenobia) I know I need some kind of triple slash directive and an import statement, but am going round in circles not getting it right.

The tsconfig in zenobia is the same as jaxom (as I show above).

The import on the client looks like this:

import * as Jaxom from 'jaxom';

so I would expect all the definitions exported to be available on Jaxom. This import should work because its typescript. If it was js, then this wouldn't work for Nodejs projects.

The documentation I have read is either ambiguous to me, or the explanation I require sits somewhere in between webpack and typescript with neither explaining the full integration properly or making assumptions.

In Jaxom, I have 2 webpack configs (1 for the production code and the other for the tests). This is the pattern I've used/am using for all my webpack/typescript projects.

production webpack config for Jaxom:

module.exports = env => {
  const { getIfUtils } = require('webpack-config-utils');
  const nodeExternals = require('webpack-node-externals');
  const { ifProduction } = getIfUtils(env);
  const mode = ifProduction('production', 'development');

  console.log('>>> Jaxom Webpack Environment mode: ' + env.mode);
  return {
    entry: {
      index: './lib/index.ts'
    },
    target: 'node',
    externals: [nodeExternals()],
    mode: mode,
    module: {
      rules: [
        {
          test: /\.ts(x?)$/,
          use: 'ts-loader'
        },
        {
          test: /\.json$/,
          use: 'json-loader'
        }
      ]
    },
    plugins: [
      new webpack.DefinePlugin({ 'process.env.NODE_ENV': '"production"' }),
      new webpack.BannerPlugin({
        banner: '#!/usr/bin/env node',
        raw: true
      })
    ],
    resolve: {
      extensions: ['.ts', '.json']
    },
    watchOptions: {
      ignored: /node_modules/
    },
    output: {
      filename: 'jaxom-bundle.js',
      sourceMapFilename: 'jaxom-bundle.js.map',
      path: path.join(__dirname, 'dist'),
      libraryTarget: 'commonjs'
    }
  };
};

And the webpack config for the tests

module.exports = {
  devtool: 'source-map',
  mode: 'development',
  entry: ['./tests/all-tests-entry.js', './lib'],
  target: 'node',
  externals: [nodeExternals()],
  module: {
    rules: [
      {
        test: /\.xml$/i,
        use: 'raw-loader'
      },
      { test: /\.ts(x?)$/, loader: 'ts-loader' },
      { test: /\.json$/, loader: 'json-loader' }
    ]
  },
  resolve: {
    extensions: ['.ts', '.json']
  },
  watchOptions: {
    ignored: /node_modules/
  },
  output: {
    filename: 'jaxom-test-bundle.js',
    sourceMapFilename: 'jaxom-test-bundle.js.map',
    path: path.resolve(__dirname, 'dist'),
    libraryTarget: 'commonjs'
  }
};
like image 940
Plastikfan Avatar asked Dec 04 '19 15:12

Plastikfan


1 Answers

For other beginners stumbling on this issue, the problem of not being able to consume types from the clients perspective was caused by a multitude of issues in the source package, explained as follows.

1) Actually, there was an issue only made apparent by the package.json which was not included in the original post, but it amounted to this:

  • there are 2 build streams, 1 for the source code and the other for tests (in my package.json these are represented by the 2 separate scripts: "build:d" for dev source code [produces ./dist/jaxom-bundle.js] build and "build:t" for test code build [produces ./dist/jaxom-test-bundle.js]. Both of these scripts depend on a "clean" script which makes use of rimraf to remove existing built files (in "dist/" folder). With my current build setup, building the tests removed all existing built files then built the test bundle. The source bundle was missing.

  • This meant that when I went to package the build (npm pack to create the tar ball that is published to npm registry), if I had just run the test build prior to publish, the test bundled which includes all the tests would be packaged. This is not what I intended. The simple fix for this scenario was to ensure that just prior to npm pack/publish, the source bundle should be built and this makes itself apparent when you look at the build: ie, you can see the included type files. For those devs just starting out this is really important to realise. Before publishing an npm package (particularly for a new package), you should go through this step of using npm pack, and then extract its contents to see what's in it, or simply take good notice of the result of npm pack and observe whats in the package.

Take a look at the following output of npm pack for the source and test bundles:

source bundle (./dist/jaxom-bundle.js):

λ ~/dev/github/js/jaxom/ feature/define-exports* npm pack
npm notice 
npm notice 📦  [email protected]
npm notice === Tarball Contents === 
npm notice 1.1kB   LICENSE                                           
npm notice 165.5kB dist/jaxom-bundle.js                              
npm notice 3.2kB   package.json                                      
npm notice 99B     README.md                                         
npm notice 455B    typings/exceptions.d.ts                           
npm notice 133B    typings/index.d.ts                                
npm notice 1.1kB   typings/normaliser/normaliser.class.d.ts          
npm notice 1.8kB   typings/specService/spec-option-service.class.d.ts
npm notice 9.4kB   typings/transformer/transformer.class.d.ts        
npm notice 2.5kB   typings/types.d.ts                                
npm notice 1.3kB   typings/converter/xpath-converter.class.d.ts      
npm notice 5.6kB   typings/converter/xpath-converter.impl.d.ts       
npm notice === Tarball Details === 
npm notice name:          jaxom                                   
npm notice version:       0.0.1                                   
npm notice filename:      jaxom-0.0.1.tgz                         
npm notice package size:  50.5 kB                                 
npm notice unpacked size: 192.2 kB                                
npm notice shasum:        c43be4000932201c0ca077aeb3f102bd0130eef5
npm notice integrity:     sha512-96HaZbSHn7kCc[...]rS0K4pLxDMLPA==
npm notice total files:   12                                      
npm notice 
jaxom-0.0.1.tgz
λ ~/dev/github/js/jaxom/

test bundle (./dist/jaxom-test-bundle.js) polluted with test classes (spec.d.ts):

λ ~/dev/github/js/jaxom/ feature/define-exports* npm pack
npm notice 
npm notice 📦  [email protected]
npm notice === Tarball Contents === 
npm notice 1.1kB   LICENSE                                                      
npm notice 165.5kB dist/jaxom-bundle.js                                         
npm notice 186.0kB dist/jaxom-test-bundle.js                                    
npm notice 3.2kB   package.json                                                 
npm notice 253.4kB dist/jaxom-test-bundle.js.map                                
npm notice 99B     README.md                                                    
npm notice 455B    typings/exceptions.d.ts                                      
npm notice 455B    typings/lib/exceptions.d.ts                                  
npm notice 133B    typings/index.d.ts                                           
npm notice 133B    typings/lib/index.d.ts                                       
npm notice 1.1kB   typings/lib/normaliser/normaliser.class.d.ts                 
npm notice 1.1kB   typings/normaliser/normaliser.class.d.ts                     
npm notice 11B     typings/tests/normaliser/normaliser.class.spec.d.ts          
npm notice 1.8kB   typings/lib/specService/spec-option-service.class.d.ts       
npm notice 1.8kB   typings/specService/spec-option-service.class.d.ts           
npm notice 20B     typings/tests/specService/spec-option-service.class.spec.d.ts
npm notice 329B    typings/tests/test-helpers.d.ts                              
npm notice 9.4kB   typings/lib/transformer/transformer.class.d.ts               
npm notice 9.4kB   typings/transformer/transformer.class.d.ts                   
npm notice 20B     typings/tests/transformer/transformer.class.spec.d.ts        
npm notice 2.5kB   typings/lib/types.d.ts                                       
npm notice 2.5kB   typings/types.d.ts                                           
npm notice 1.3kB   typings/converter/xpath-converter.class.d.ts                 
npm notice 1.3kB   typings/lib/converter/xpath-converter.class.d.ts             
npm notice 20B     typings/tests/converter/xpath-converter.class.spec.d.ts      
npm notice 5.6kB   typings/converter/xpath-converter.impl.d.ts                  
npm notice 5.6kB   typings/lib/converter/xpath-converter.impl.d.ts              
npm notice 20B     typings/tests/converter/xpath-converter.impl.spec.d.ts       
npm notice === Tarball Details === 
npm notice name:          jaxom                                   
npm notice version:       0.0.1                                   
npm notice filename:      jaxom-0.0.1.tgz                         
npm notice package size:  115.6 kB                                
npm notice unpacked size: 654.3 kB                                
npm notice shasum:        8433700d3378bf2ab4ff3798a93e7dbb98baee86
npm notice integrity:     sha512-cQ9RUCLwHMOuq[...]xS4kMJe/4jPqQ==
npm notice total files:   28                                      
npm notice 
jaxom-0.0.1.tgz
λ ~/dev/github/js/jaxom/

I think the best solution to the above issue would be to have a specific publish script that builds the correct bundle prior to publishing to npm. I found a couple of good articles than explain this well: Step by step: Building and publishing an NPM Typescript package or The 30-second guide to publishing a TypeScript package to NPM

2) Types in a separate folder (my bad!).

  • having read a plethora of articles on the subject, I came to the miguided conclusion that is was a good idea to put the generated types into a separate folder than the resultant ./dist js bundles. This just makes using the library for the user more awkward and not intuitive to use. Instead of importing jaxom with: "import * as jaxom from 'jaxom'", you would need to use "import * as jaxom from 'jaxom/typings'" (where "typings" is the name of the directory you export types to defined by type/typings entry in jaxom's package.json. This is just not a good idea and not what I intended.

Best just let the types be generated into the same folder as your main bundle, in this case ./dist.

like image 66
Plastikfan Avatar answered Nov 15 '22 04:11

Plastikfan