构建NPM软件包需要许多需要考虑的决定。从选择默认的捆绑器到选择合适的转板器到了解工具或软件包的用例,或者我敢说,您正在创建库。
与编写代码的实际过程相比,这一过程中有很多思考。是的,有时候,整个“深思熟虑” charade甚至可能没有必要。但是,重要的是要把它放在脑海。
webpack,老人
几年前,构建这些包裹的一种方法完全取决于使用webpack作为首选bundler,它还带有许多插件,可用于捆绑您的JavaScript代码。
。但是,WebPack附加了许多复杂性。当我尝试使用几次时,我总是会对在配置文件I even wrote an article about my travails中飞来的事物数量感到沮丧。
注意:我在这里的目的不是要对webpack进行大声疾呼,而是为构建这些包裹的更微妙的方法和发布。
很多人可能会在这里提出我的意见,并提出著名的报价“ webpack用于构建应用程序,其他人用于构建图书馆” 。
但是,我认为这里有很多人错过的是,与WebPack相比,“其他”捆绑工具具有的良好DX开发人员体验。
是的,WebPack为您提供了很多功能。好的。但是不利于我的理智?
正如我之前提到的,本文的目的某种程度上围绕着我犯的错误以及如何绕过它们。
已经有几天了,我一直在处理一个React Tab component,该React Tab component在单击时保留每个选项卡的状态(或NAV项目),当您从和返回到安装该组件的位置时。 P>
处理它肯定了一个我一直在思考很长时间的问题。 “我们可以将组件状态保持在浏览器URL中吗?”
您应该尝试使用包裹,让我知道您的想法。
我写了一篇关于为什么you might not need a state-management library的简短文章,因为它为该组件建立了概念概念。在业余时间看看。
现在,在手头的问题上,NPM软件包。
从我过去所做的事情中,我认为我可以得出结论,当您决定构建不在任何不在任何中类似于依赖CSS某种样式的组件库或软件包。
一个示例是这个helper function,它从任何一段时间以前从任何smarkdown文件中提取标题文本。您可以查看编译器here
的配置让我介绍您完成此配置的本质。它与仓库中的一个相同。
{
"include": ["src"],
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"lib": ["ESNext"],
"module": "ESNext",
"sourceMap": true,
"importHelpers": true,
"declaration": true,
"rootDir": "./src",
"outDir": "./dist/esm",
"strict": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
include
指定编译打字稿代码时应包含的文件或目录。在这种情况下,对于该辅助功能,它包括src
目录。您的可能是不同的。
exclude
指定了应从汇编过程中排除的文件或目录。在这里,它不包括node_modules
和dist
目录。
在compilerOptions
部分中,有lib
属性可确保应包含在编译过程中的特定库。
在这里,它包括“ esnext”库,该库提供了最新的ecmascript功能。
module
属性确定要使用的模块系统。在这种情况下,它设置为“ esnext”,它可以使用现代JavaScript模块。
sourceMap
启用了生成源地图,这对于在浏览器或开发工具中调试打字条代码很有用。
importHelpers
使打字稿助手函数的进口能够帮助某些功能,例如装饰器和异步/等待。
declaration
与编译的JavaScript文件一起生成相应的.d.ts
声明文件,允许在消耗此代码的其他打字稿项目中进行键入检查和代码完成。
rootDir
为打字稿源文件指定根目录。在这里,它设置为src
目录。同样,您的可能是完全不同的。
在最近的软件包中,我一直在工作。我将我的矿山设置为packages
,因为库将有许多不同组件的用例,据推测。
outDir
确定编译JavaScript文件的输出目录。在这种情况下,它设置为dist/esm
目录。
strict
启用严格的类型检查和更严格的编译器选项。
noImplicitReturns
报告函数缺少返回语句时的错误。
noUnusedLocals
此报告当声明本地变量但未使用本地变量时发生错误。
noUnusedParameters
在声明但未使用函数参数时报告错误。
moduleResolution
指定了打字稿如何解决模块导入。在这里,它设置为使用node.js模块分辨率的“ node”。
有时这部分往往非常令人沮丧。
jsx
确定用于JSX的语法。在这种情况下,它设置为“反应”以支持React JSX语法。
esModuleInterop
可以在commonjs和es ES模块之间互操作性,从而更轻松地在打字稿中导入commonjs模块。
skipLibCheck
跳过依赖性的声明文件(*.d.ts)的样式检查,可以提高编译速度。
forceConsistentCasingInFileNames
强制执行一致的文件名外壳,这可以帮助防止在不同操作系统上工作时的问题。
如果您不想,则不必严格使用此配置。您可以自由设置对您有效的任何一个。
输入,rollup.js
lolup是用于JavaScript应用程序的捆绑器,如果您想构建组件库或任何NPM软件包,它是迄今为止使用的最建议的工具,因为与WebPack相比,它具有直观而直接的启动开发过程。<<<<<<<<<<<<<<<<<<<<<<<<<<<< /p>
这里的好处是,当您想构建NPM软件包时,您可以一起使用Typescript和汇总。尤其是一个取决于样式的人。
您需要做的就是找到合适的插件。当我着手构建react-tab软件包时,我从打字稿的编译器tsc
作为我的Bundler开始。
但是,它带来了很多局限性,因为它无法正确提取和处理CSS输出。由于Rollup具有我可以使用的插件生态系统,因此进行研究并发现合适的插件并不太艰难。
还记得我还如何谈论创建理想软件包的“思考”吗?
如果您要构建的NPM软件包以一种或另一种方式取决于使用CSS,则可能需要考虑选择您采用的方法。
有些人喜欢使用CSS模块,一些带有SCS或SASS的模块,有些则具有普通的CSS。我为react-tab
“你为什么要这样做?样式组件有很多限制”
我稍后再解决。我的观点围绕着那些小边缘的案例展开。例如,现在,如果您决定将CSS模块用于主要最终用户可能会在Next.js项目中使用它的软件包,那么您将有一些问题要处理。
使用样式组件对我来说是一个明智的决定,因为Tab组件本来可以直观且完全可以自定义。我需要暴露某些样式道具。
,使用样式组件时可以扩展组件的道具是不是新事物。
// component.styled.ts
import styled from "styled-components"
type themeProps = {
theme: string
}
export const Container = styled.div<themeProps>`
background: ${({theme}) => theme ? theme : "brown"}
`
Container
变成类似于下面的摘要的东西。这只是通过此CSS组件图
可以实现的冰山的尖端
<Container theme="purple">
// children
</Container>
但是,限制仍然存在。因为,正如我之前提到的,打字稿的编译器无法正确处理将捆绑在一起的样式的输出,请滚动以保存一天。
真相的来源 - config.js
对于大多数捆绑器来说,拥有一个配置文件是一种常见的做法,该文件可以控制应用程序的块,处理,编译,以及在幕后进行的任何其他操作。
可以在下面看到配置文件的示例。这将导出应用程序和输出目录的入口点。
export default {
input: "packages/index.ts",
output: {
file: "dist/index.js",
format: "cjs"
}
}
由于react-tab使用外部依赖关系,因此,next/router
是确切的,当我尝试使用rollup -c
命令构建包装时,将记录“未解决的依赖关系”警告。
汇总根据其导入路径以不同的方式对待依赖性。当它遇到以.
或/
开头的导入时,它假定它是本地文件并相应地解决它。
但是,当导入以模块名称(例如next/router
)开头时,它将其视为外部依赖项,应通过使用包装的人来解决。
要绕过此警告,我必须指定next/router
是我的汇总配置中的外部依赖性。
同样的方法适用于您,一旦您确定了您的应用程序/库依赖的外部软件包。
export default {
input: "packages/index.ts",
output: {
file: "dist/index.js",
format: "cjs",
},
external: ["react", "react-dom", "next/router"],
}
回想一下,当我提到与样式组件的局限性有关的东西时?这是它的问题。
在共享组件或库中使用样式组件时,您应该牢记三件事,以确保在其他项目消耗软件包时正确应用样式。
将其列为对等依赖关系:我必须确保styled-components
在我的软件包文件中列为同行依赖。
这通知消费者,他们需要在自己的项目中安装styled-components
才能正确利用您的组件。
如果您在package.json
文件中没有这样的方式,则可以通过使用generatePackageJSON
插件来修改peerDependencies
属性来这样做:
generatePackageJSON({
outputFolder: "dist",
baseContents: (pkg) => ({
name: pkg.name,
main: "/dist/index.js",
peerDependencies: {
react: "^18.2.0",
"styled-components": "^6.0.0-rc.3",
},
}),
})
CSS提取:默认情况下,样式组件通过在运行时将其注入DOM来应用样式。
但是,当构建共享组件库或软件包时,最好在构建过程中提取样式,因此消费项目可以捆绑并使用它们。
为了实现这一目标,我必须在我的Rollup Config
中使用像babel-plugin-styled-components
这样的工具以及Babel插件
babel({
extensions: [".ts", ".tsx"],
exclude: "node_modules/**",
presets: ["@babel/preset-react", "@babel/preset-typescript"],
plugins: ["styled-components"],
}),
css output :只要您编写任何被转移到CSS的内容,都应包含一个croup插件rollup-plugin-styles
。 P>
插件可确保正确提取您要编写的样式。
export default {
input: "packages/index.ts",
output: {
file: "dist/index.js",
format: "cjs",
},
external: ["react", "react-dom", "next/router"],
styles(),
}
在peerDependencies
属性中,依赖项的确切版本是硬编码的。但是,有一种方法可以确保同行依赖性自动更新到最新兼容版本而不手动硬编码。
您可以在同行依赖项的版本范围内使用latest
标签。
peerDependencies: {
react: "latest",
"styled-components": "latest",
},
通过使用latest
标签,它将自动获取满足指定版本范围的同行依赖的最新版本。
这样,您无需每次兼容版本时都需要手动更新版本。
当用户安装您的软件包或运行npm install
或yarn install
时,软件包管理器将根据您的package.json
中指定的版本范围获取同伴依赖项的最新兼容版本。
但总会有一个,但请记住,如果发布新的同行依赖性新的主要版本,并且包括破坏变化,则使用latest
标签也可以引入潜在的破坏更改。
为了减轻这种情况,在发布新版本之前,最好用最新版本的同行依赖项彻底测试您的软件包。
这是完整的汇总配置的样子:
import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import generatePackageJSON from "rollup-plugin-generate-package-json";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import styles from "rollup-plugin-styles";
import { terser } from "rollup-plugin-terser";
const dev = process.env.NODE_ENV !== "production";
export default {
input: "packages/index.ts",
output: {
file: "dist/index.js",
format: "cjs",
},
external: ["react", "react-dom"],
plugins: [
nodeResolve({
extensions: [".ts", ".tsx"],
}),
babel({
extensions: [".ts", ".tsx"],
exclude: "node_modules/**",
presets: ["@babel/preset-react", "@babel/preset-typescript"],
}),
generatePackageJSON({
outputFolder: "dist",
baseContents: (pkg) => ({
name: pkg.name,
main: "/dist/index.js",
peerDependencies: {
react: "^18.2.0",
"styled-components": "^6.0.0-rc.3",
},
}),
}),
terser({
ecma: 2015,
mangle: { toplevel: true },
compress: {
toplevel: true,
drop_console: !dev,
drop_debugger: !dev,
},
output: { quote_style: 1 },
}),
commonjs(),
styles(),
],
};
这是package.json
文件的重要部分所需要的。它为react-tab软件包指定了构建配置和脚本。
您可以采用它适合您的用例。
{
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts",
"scripts": {
"build:esm": "tsc",
"build": "yarn build:esm && yarn build:cjs",
"build:cjs": "rollup -c"
},
}
这是摘要的分解;
"main": "./dist/cjs/index.js"
指定COMPORJS(CJS)模块的入口点。当某人使用require()或在commonjs环境中导入您的软件包时,这是将加载的文件。
"module": "./dist/esm/index.js"
指定了eCmascript模块(ESM)的入口点。当某人使用在ESM支持的环境中导入您的软件包时,这是将加载的文件。
"types": "./dist/esm/index.d.ts"
指定了打字稿声明文件的位置(.d.ts
)。
此文件为您的软件包提供类型信息,允许打字稿用户在使用软件包时获得类型检查和自动完成。
"build:esm": "tsc"
运行打字稿编译器tsc
,以构建软件包的ESM版本。它将打字稿代码编译到JavaScript中,并在./dist/esm
目录中输出文件。
"build": "yarn build:esm && yarn build:cjs"
首先运行build:esm
脚本,然后执行build:cjs
脚本。
它可以确保构建包装的ESM和CJS版本。
"build:cjs": "rollup -c"
使用配置文件(-c
)运行汇总bundler rollup
。
汇总读取配置文件以捆绑代码,应用任何指定的转换或优化。输出是在./dist/cjs
目录中生成的。
出版前在本地进行测试。
此部分有些棘手,因为它可能会以某种方式影响工具的版本控制过程。
您不必随时随地运行npm publish
,而应与npm link
如果您使用的是任何Linux发行版OS,则必须将sudo
附加到npm link
才能授予权限。这样可以确保为软件包创建符号链接。
然后,您可以继续在您想这样使用的另一个项目中链接软件包。
npm link package-name
包起来。
因为本文的意图并不完全是通过遵循一些步骤来引导您完成构建组件库的过程,以下是一些资源可以帮助您实现目标。
How to build a component library with React and TypeScript