View: the original marcobotto.com/blog/compil…
Making: github.com/Yingkaixian…
Use the TypeScript library in JavaScript
We’ll find that writing libraries and application code are two very different experiences. They serve different purposes and goals: the application code is targeted at the user of the application and usually does not interact with the underlying code, while the library is targeted at other developers, so it needs to have a better reusability and developer experience.
In addition to affecting our code style and architecture (which should be a separate topic in the future), this difference has a direct impact on our tools. It’s important to keep a few practices in mind when developing libraries with TypeScript in order to provide best support for other developers and avoid unnecessary difficulties.
Today, any code written for the front end (browser) must be compatible with the current situation, namely (even in 2017) the old ES5 specification.
TypeScript ships with the TSC (TS compiler) to handle this: it compiles code to ES5 and creates *.d. TS files containing type declarations to preserve TypeScript support.
What about users who don’t rely on packaging tools, such as Webpack and Rollup?
How does Tree Shaking work when using packaging tools?
We can achieve compatibility with all of these different Settings through a small but efficient pipeline, which is what we used in the UI-Router.
Webpack + tsc + npm script = ♥️
Our goal is to create three different outputs:
-
TSC compiled code + *.d. TS type declaration + source map (very easy to debug TS source code). The module syntax will be CommonJS, used to support most packaging tools.
-
As above, use the ES6 module syntax so that the latest tool (Webpack 2 / Rollup) can perform Tree shaking to reduce the size of packaged files.
-
A UMD package compiled to standard ES5 that works in a browser and exposes global variables.
Usually we do not need to provide a TS-related type declaration file in the compiled code for UMD, because UMD is defined to be a generic library. Type declaration files are not valid JavaScript files (because of the.ts extension) and cause problems in packages when used in browsers and non-TS environments.
This way, we can retain full support for TS applications and common module packaging tools, while falling back to UMD when needed.
We solved the first two solutions by using TSC and then Webpack to solve the third solution. We can wrap this as an NPM script with the help of SHX.
The directory structure
Imagine a library directory structure like this:
node_modules/
src/
package.json
README.md
tsconfig.json
yarn.lock
Copy the code
The code we want to release on NPM is the three compiled copies of the code we just discussed, along with several other files:
_bundles/ // UMD bundles
lib/ // ES5(commonjs) + source + .d.ts
lib-esm/ // ES5(esmodule) + source + .d.ts
package.json
README.md
Copy the code
To exclude the rest of the files, NPM accepts an.npmignore identical to.gitignore. If we don’t provide this, NPM will ignore the contents of.gitignore, which of course is not what we want.
It is recommended to use the files field in package.json to specify the files you want to publish to NPM.
You can find the code in this repository, as it may be useful as a reference for the rest of the article.
TypeScript configuration
TS only needs a tsconfig.json file to configure the compiler options and specify that the folder is a TS project.
There are a number of configuration options that may vary depending on the needs of the library. Options to focus on include:
{
"module": "commonjs"."target": "es5"."lib": ["es2015"."dom"]."outDir": "lib"."sourceMap": true."declaration": true
}
Copy the code
“module”: “commonjs”
We tell the compiler that by default we want our module declarations to use CommonJS syntax. This compiles each import and export to require() and module.exports declarations, which is the default syntax in the NodeJS environment.
“target”: “es5”
The object code will be ES5 syntax because, as we explained earlier, we need to provide code that can run without further compilation/transformation.
“lib”: [ “es2015”, “dom” ]
Lib is the special declaration file that TS contains. It contains JS at run time or DOM environment, etc.
TS automatically includes DOM and ES5 syntax, depending on target. That’s why we need to specify that the types we need are ES2015 and DOM.
This allows us to use all ES6 types at the same time in our output ES5 code.
“outDir”: “lib”
As mentioned earlier, the compiled code is saved to the lib folder.
“sourceMap”: true & “declaration”: true
We also need source maps from our source code as well as the declaration file.
With the above configuration, when we run the TSC command, the compiler will create the first of the three outputs for us.
We could create a second tsconfig file for the second project, but since the configuration is almost identical, we’ll override these options using the command line –flag:
tsc -m es6 --outDir lib-esm
Copy the code
The compiler will use the ES6 module instead of CommonJS (-m ES6) and put it in the specified folder (–outDir lib-esm).
We can combine these two commands by executing TSC && TSC -m es6 –outDir lib-esm.
Webpack configuration
The final piece of the puzzle is the Webpack, which will do the packaging for us. Since Webpack doesn’t understand TypeScript, we need to use loader, just as we used babel-loader to specify that WebPack compile source through Babel.
There are several TS loaders, and we finally decided to use awesome-typescript-loader for several reasons: It’s faster, by assigning type checking and triggers to a separate process, and has good integration with Babel in case you need to further compile your code using some convenient Babel plug-ins (which we won’t discuss in this article).
The Webpack configuration file varies from project to project because it is a very powerful tool that developers use to do all sorts of things. But for the (relatively simple) library, all we need to do is package the ES5 syntax code (compiled code using the configuration output in TSConfig above) into two separate files (a compressed version).
Webpack and Loader will also create a Source map containing the original TS code files, which is handy for debugging.
I’m assuming you’re already familiar with Webpack, or at least you’ve tried it before. If you have any questions, check out the new version 2 website for an in-depth look at the concepts behind the tool as well as examples and documentation.
The basic configuration looks like this:
{
"entry": {
"my-lib": "./src/index.ts"."my-lib.min": "./src/index.ts"
},
"output": {
"path": path.resolve(__dirname, "_bundles"),
"filename": "[name].js"."libraryTarget": "umd"."library": "MyLib"."umdNamedDefine": true
},
"resolve": {
"extensions": [".ts".".tsx".".js"]},"devtool": "source-map"."plugins": [
new webpack.optimize.UglifyJsPlugin({
"minimize": true."sourceMap": true."include": /\.min\.js$/})]."module": {
"loaders": [{"test": /\.tsx? $/."loader": "awesome-typescript-loader"."exclude": /node_modules/."query": {
"declaration": false}}]}}Copy the code
This is a standard Webpack configuration, but keep the following points in mind:
output
We tell Webpack that we are packaging a library by setting some properties. This value is the name of the library. The libraryTarget and umdNamedDefine tell Webpack to create a UMD module and name it after the lib we just set up.
extensions
Webpack usually treats.js as a module, so we need to tell it to also look for.ts and.tsx files (if you’re using React + JSX).
module
Finally, we use awe-typescript-loader to parse the source code. It is important here to use query parameters to customize the ATL and turn off the type declaration output. The loader will use the tsconfig.json file to indicate the compiler, but everything we define here will override the configuration file.
Atl stands for awesome-typescript-loader.
Tip: If we decide to use React and JSX syntax, just add the regular expression /\.tsx? $/ Matches.ts and.tsx files.
devtool & plugins
Source-map generates a code map used by a build environment, with source location information and so on. The compressed version will be created by UglifyJSPlugin, so we must specify the sourcemap we want because the plug-in default is false.
Once the configuration files are set up, if we run Webpack in the terminal, it should create the \_bundles folder, which contains our four files:
my-lib.js
my-lib.js.map
my-lib.min.js
my-lib.min.js.map
Copy the code
Contains all the
Now that we have everything set up, we can create several NPM scripts to automate the process and clean up the folder each time we compile.
I like using SHX because it’s cross-platform, so I don’t have to worry about which operating system I’m using.
The clean script is responsible for deleting the new version of the folder (if it exists) :
shx rm -rf _bundles lib lib-esm
Copy the code
Build scripts care about how to put things together:
npm run clean && tsc && tsc -m es6 --outDir lib-esm && webpack
Copy the code
In order: It will remove any of the three folders, if they exist, instead of building all three versions.
.gitignore & .npmignore
To process the repository and publish to the NPM registry, we must remember to add something to the ignore file:
.gitignore:
node_modules
lib
lib-esm
_bundles
Copy the code
Everything that is a module or build should not remain in the repository, because we can build it locally as needed.
.npmignore:
.*
**/tsconfig.json
**/webpack.config.js
node_modules
src
Copy the code
The end user will be using the compiled file (regardless of version), so we can remove the entire source code, dependencies, and TS/Webpack configuration.
These files don’t cause any problems, but it’s best to keep the number of files to a minimum because it will be downloaded every time someone uses it, which just wastes bandwidth and storage space.
conclusion
I’m sure there are many other configurations that would work, but the current features are good enough for us and can be easily configured to meet more complex requirements.
Ideally, there are only two outputs: commonJS and es modules. If there is currently no way to create type definitions (mirroring packaged UMD modules) in a single package file, the UMD file will meet our requirements as CommonJS.
In addition, since TypeScript 1.8, the compiler has added the option (-outfile) to iterate over importing individual packaged files. I prefer using Webpack because it gives me more control over the transcoding pipeline (and just because I’m used to Webpack).
copyright
Author: Ying Kaixiang
Link: github.com/Yingkaixian…
Source: Personal blog
Copyright belongs to the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.