Reputation: 1975
I am creating a modular component library with React and TypeScript with Babel 7.
I want the user of my library to import the components by a syntax similar to this:
import SomeComponent from "my-awesome-lib/SomeComponent"
SomeComponent is a TSX module in my-awesome-lib package:
import * as React from "react";
export default function () {
return <h1>SomeComponent</h1>
}
And main
field of the package.json file of my-awesome-component is:
"main": "src/index.ts"
I do not want to publish compiled version of my component library, because I am importing CSS and other assets in my components and I expect all the users of my package to use Webpack with some specific configs.
Now my problem is that `import SomeComponent from "my-awesome-lib/SomeComponent" fails with a parse error:
ERROR in ../node_modules/wtf/index.tsx 4:9
Module parse failed: Unexpected token (4:9)
You may need an appropriate loader to handle this file type.
|
| export default function () {
> return <h1>WTF</h1>
| }
It seems Webpack does not load or transform the TSX files in node_modules.
I use this tsconifg.json at the root of my user app (which imports my-awesome-lib):
{
"compilerOptions": {
"outDir": "./dist",
"module": "commonjs",
"target": "es5",
"jsx": "react",
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"downlevelIteration": true,
"lib": ["es5", "es2015", "dom", "scripthost"],
"typeRoots": ["node_modules/@types", "src/@types"],
},
"include": ["src/**/*", "node_modules/**/*"],
"exclude": []
}
And the relevant configurations of Webpack are:
const tsModules = {
test: /\.(js|jsx|ts|tsx)$/,
include: [path.resolve('src')],
exclude: /node_modules/,
loader: 'babel-loader'
}
const resolve = {
alias: {
},
modules: [
'node_modules'
],
extensions: ['.tsx', '.ts', '.js']
}
module.exports = {
...
context: resolve('src'),
resolve: resolve,
module: {
rules: [
tsModules,
...
]
}
}
How can I make Webpack to load and transform TSX modules of my-awesome-lib from node_modules?
Upvotes: 1
Views: 4146
Reputation: 2673
You are excluding your node_modules
directory (which generally is a good thing):
const tsModules = {
test: /\.(js|jsx|ts|tsx)$/,
include: [path.resolve('src')],
exclude: /node_modules/,
loader: 'babel-loader'
}
Besides explicitely excluding the node_modules
folder, you also only allow the content of the src
folder to be processed by babel-loader
because of your include
property in tsModules
. So this error:
ERROR in ../node_modules/wtf/index.tsx 4:9 Module parse failed: Unexpected token (4:9)
makes sense.
You can still exclude node_modules
except for a single folder if you remove the tsModules.include
property and change your regular expression in tsModules.exclude
:
const tsModules = {
// ...
exclude: /node_modules\/(?!my-awesome-lib)\/*/
// ...
}
Or you could, but I haven't tested it yet, add the my-awesome-lib
dir to the include
array:
const tsModules = {
// ...
include: [
path.resolve(__dirname, './src'),
path.resolve(__dirname, './node-modules/my-awesome-lib')
]
// ...
}
Then files in your node_modules/my-awesome-lib
directory will pass the babel-loader
which will transform the typescript code.
Edit: I think you confusion is coming from your tsconfig.json
file with "include": ["src/**/*", "node_modules/**/*"],
. Babel is transpiling your code, not typescript. So having a tsconfig.json
file in your root directory may help your IDE (especially if you are using Microsoft's VScode), but has no effect on how babel
and @babel/preset-typescript
transform your code.
Upvotes: 1
Reputation: 6581
This setup assuming you are using styled-components and have no css/scss.
What's important here is that you have "module": commonJS in your tsconfig and libraryTarget: "commonJS" in your webpack config. Externals tells webpack not bundle you're library with React, or React-DOM or styled-components and instead to look for those packages within the project you're importing into.
you're also going to need to take React, react-dom and styled-components out of your package.json dependencies and put those package in your peer-dependencies
const path = require("path");
const fs = require("fs");
const TerserPlugin = require('terser-webpack-plugin');
const appIndex = path.join(__dirname, "../src/main.tsx");
const appBuild = path.join(__dirname, "../storybook-static");
const { TsConfigPathsPlugin } = require('awesome-typescript-loader');
module.exports = {
context: fs.realpathSync(process.cwd()),
mode: "production",
bail: true,
devtool: false,
entry: appIndex,
output: {
path: appBuild,
filename: "dist/Components.bundle.js",
publicPath: "/",
libraryTarget: "commonjs"
},
externals: {
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react'
},
'react-dom': {
root: 'ReactDOM',
commonjs2: 'react-dom',
commonjs: 'react-dom',
amd: 'react-dom'
},
"styled-components": {
root: "styled-components",
commonjs2: "styled-components",
commonjs: "styled-components",
amd: "styled-components"
}
},
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
parse: {
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
},
mangle: {
safari10: true,
},
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
},
parallel: true,
cache: true,
sourceMap: false,
})
],
},
resolve: {
extensions: [".web.js", ".mjs", ".js", ".json", ".web.jsx", ".jsx", ".ts", ".tsx"],
alias: {
"react-native": "react-native-web",
},
},
module: {
strictExportPresence: true,
rules: [
{ parser: { requireEnsure: false } },
{
test: /\.(ts|tsx)$/,
loader: require.resolve("tslint-loader"),
enforce: "pre",
},
{
oneOf: [
{
test: /\.(tsx?)$/,
loader: require.resolve('awesome-typescript-loader'),
options: {
configFileName: 'tsconfig.prod.json'
}
},
],
},
],
},
plugins: [
new TsConfigPathsPlugin()
],
node: {
dgram: "empty",
fs: "empty",
net: "empty",
tls: "empty",
child_process: "empty",
},
performance: false,
};
note: it's important that you target a point in you're application as an entry that ONLY has the components you want to export.
I.E For me it's Main.tsx and inside Main.tsx it looks like this.
export { Checkbox } from "./components/Checkbox/Checkbox";
export { ColorUtils } from "./utils/color/color";
export { DataTable } from "./components/DataTable/DataTable";
export { DatePicker } from "./components/DateTimePicker/DatePicker/DatePicker";
export { DateTimePicker } from "./components/DateTimePicker/DateTimePicker/DateTimePicker";
export { Disclosure } from "./components/Disclosure/Disclosure";
This means webpack won't bundle things you're not meaning to export. To test you're bundle works try importing something with require syntax from the bundle to get around typescript typings and turn allowJS true in tsconfig.
something like const Button = require("../path/to/js/bundle").Button console.log(Button);
Upvotes: 1