/transpile-webpack-plugin

Transpiles input files into output files individually without bundling together

Primary LanguageTypeScriptMIT LicenseMIT

Transpile Webpack Plugin

npm node download license size cicd

Transpile Webpack Plugin

The webpack plugin that transpiles input files into output files individually without bundling together.

Input files are collected from files directly or indirectly imported by the entry, then get compiled and ouputted keeping the same directory structure in the output directory.

Transpiling with webpack is especially helpful when source file path based logics are involved, such as registering events in AWS Lambda, or migrations managing with Sequelize CLI.

Notice that this plugin replies on features of webpack v5. The latest webpack is supposed to be used when possible.

Getting Started

To begin, you'll need to install transpile-webpack-plugin:

npm i -D transpile-webpack-plugin

Or, any other package manager you prefer, like yarn or pnpm, would work, too.

Then, add the plugin to your webpack config. For example:

const TranspilePlugin = require('transpile-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: __dirname + '/dist',
  },
  plugins: [new TranspilePlugin(/* options */)],
};

Now, assuming in the dir src, the entry file src/index.js imports another file src/constants/greeting.js:

src
├── constants
│   └── greeting.js
└── index.js

With the webpack config above, after compilation, output files will be:

dist
├── constants
│   └── greeting.js
└── index.js

Files src/index.js and src/constants/greeting.js are collected as input files. Then, the common dir of input files is used as the base dir to evaluate the relative paths of output files in the output dir dist, which results in output files dist/index.js and dist/constants/greeting.js.

Just a reminder, if to run output files with NodeJS, don't forget to set the target as node or a node-compatible value so that no breaking code is generated by webpack unexpectedly.

Blogs

Options

exclude

Type: {string|RegExp|((p: string) => boolean)|string[]|RegExp[]|((p: string) => boolean)[]}

Default: []

Option exclude indicates files to be excluded. It's similar to the externals except that the import paths to the excluded are properly adjusted automatically. It's useful when you copy some third-party files and want to use them as they are.

Though, excluding node_modules with this option is not a good idea. Some helpers of webpack loaders have to be compiled before they become runnable. If you need to exclude dependencies in node_modules, using webpack-node-externals might be a better choice because it doesn't exclude helpers of webpack loaders.

With this option as a string (or strings), input files whose aboslute paths begin with it will be excluded. With this option as a regular expression (or regular expression), input files whose absolute paths match it will be excluded. With this option as a function (or functions), input files whose absolute paths are passed into the call of it and end up with true will be excluded.

hoistNodeModules

Type: {boolean}

Default: true

Option hoistNodeModules indicates whether to evaluate output paths for input files inside or outside node_modules separately, then keep input files from node_modules outputted into node_modules just under the output dir. It's usable to flatten the output directory structure a little bit.

Given input files src/index.js, node_modules/lodash/lodash.js and the output dir dist, with this option true, output files will be dist/index.js and dist/node_modules/lodash/lodash.js. But with this option false, output files will be dist/src/index.js and dist/node_modules/lodash/lodash.js.

longestCommonDir

Type: {string|undefined}

Default: undefined

Option longestCommonDir indicates the limit of the common dir to evaluate relative paths of output files in the output dir. When the dir that this option represents is the parent dir of the common dir of input files, this option is used against input files to evaluate relative paths of output files in the output dir. Otherwise, the common dir of input files is used.

Given input files src/server/index.js, src/server/constants/greeting.js and the output dir dist, with this option undefined, output files will be dist/index.js dist/constants/greeting.js. But with this option './src', output files will be dist/server/index.js dist/server/constants/greeting.js.

Though, given input files src/index.js, src/server/constants/greeting.js and the output dir dist, with this option './src/server', output files will still be dist/index.js dist/server/constants/greeting.js because the dir that this options represents is not the parent dir of the common dir of input files.

extentionMapping

Type: {Record<string, string>}

Default: {}

Option extentionMapping indicates how file extensions are mapped from the input to the output. By default, an output file will have exactly the same file extension as its input file. But you may change the behavior by this option. With this option { '.ts': '.js' }, any input file with ext .ts will have the output file with ext .js.

preferResolveByDependencyAsCjs

Type: boolean

Default: true

Options preferResolveByDependencyAsCjs indicates whether to try to resolve dependencies by CommonJS exports regardless of types of import statements. It's useful when the target is node because .mjs files are treated as ES modules in NodeJS and can't be required by webpack generated CommonJS files.

Given { "exports": { "import": "index.mjs", "require": "index.cjs" } } in package.json of a dependency, with this option true, either import or require to this dependency will end up with index.cjs imported. While, with this option false, import ends up with index.mjs imported and require ends up with index.cjs imported (, which is the default behavior of webpack).

Known limits

01: Can't handle circular dependencies in the same way as NodeJS.

In NodeJS, top-level logics in a file run exactly at the time when it's imported, which makes circular dependencies possible to work. Take an example of files a.js and b.js:

// In file 'a.js'
const b = require('./b');

function main() {
  b.goo();
}

function foo() {
  console.log('lorem ipsum');
}

module.exports = { foo };

main();

// In file 'b.js'

const a = require('./a');

function goo() {
  a.foo();
}

module.exports = { goo };

When a.js runs, an error of TypeError: a.foo is not a function thrown from b.js. But putting the line const b = require('./b'); just after module.exports = { foo }; resolves the problem:

// In file 'a.js'
-
-const b = require('./b');

function main() {
  b.goo();
}

function foo() {
  console.log('lorem ipsum');
}

module.exports = { foo };
+
+const b = require('./b');

main();

Though, for a webpack generated file, the real exporting is always done in the end of it. Webpack collects all the exports into an internal variable __webpack_exports__, then exports it at last, which makes circular dependencies always break.

Making circular dependencies is a bad practice. But you might have to face them if using some libs that are popular but maintained since the early releases of NodeJS, like jsdom. When this happens, please use the externals to leave the libs untouched.

02: Can't conditionally import not-yet-installed dependencies.

Webpack always detects and resolves import statements regardless of whether they run conditionally. Logics as below end up with the conditionally imported dependency colorette resolved:

function print(message, color) {
  if (typeof color === 'string') {
    message = require('colorette')[color](message);
  }
  console.log(message);
}

As a result, conditionally importing any not-yet-installed dependency causes the compile-time error of Module not found in webpack. So, either, you need to make sure the conditionally imported dependency installed. Or, use the externals to leave it untouched.

03: Can't import .node files directly.

By default, importing .node files causes the compile-time error of Module parse failed in webpack. Using node-loader along with the plugin option extentionMapping as { '.node': '.js' } resolves some very basic cases. But as the node-loader itself doesn't handle paths well, the practice is not recommeneded. Instead, you may use the externals to leave the JS files that use the .node files untouched.

Contributing

Please take a moment to read our contributing guidelines if you haven't yet done so. CONTRIBUTING

License

MIT