头图

前言

纯前端项目和nodejs项目打包方式有很大的不同,本文的重点是如何在nodejs环境中使用react,并打包成二进制文件.

我们通常开发前端项目时使用的各种cli一般都是这么安装的 npm install xxxcli ,这样安装后就能使用相应的xxxcli, 但是查看对应的cli文件;
如安装完本示例后就会现出 /usr/local/bin/ink-app这个文件, 查看此文件内容就会发现是nodejs代码,所以是依赖nodejs的运行环境的.

而想把nodejs项目真正转出二进制包,需要pkg命令把nodejs运行环境也打包进去...这就要解决react及其jsx语法带来的问题.

查了一会发现没有这么操作的,但这里有个项目wiki-cli 就是通过 node+react+ink 构建的一个cli项目,可以看到源码中有bin目录并且有可用的wiki二进制命令那就说明上面的思路是可行的.这就引起了我的兴趣.

示例

按下面链接中的步骤操作生成示例项目

$ mkdir ink-app
$ cd ink-app
$ npx create-ink-app

cli.js 文件大概如下,其中的注释和中文都是我后面理清思路后加的,默认示例中是没有的.

// cli.js
#!/usr/bin/env node
// 'use strict'; // 不启用严格模式

// 这些都是错误的
// require("babel-register") 
// require("register")
// 加这句是才不能解决
// require('@babel/register');

const React = require('react');
const importJsx = require('import-jsx');
const { render } = require('ink');
const meow = require('meow');

// node v12.22.0 支持此语法
const myFunc = () => {
    console.log('hello world');
}
myFunc();

// 其他的文件必须都得通过这种形式导入,并且此文件代码是正常的nodejs代码而不能包含jsx
// 但打包时这里经常会出现找不到ui文件的错误
const ui = importJsx('./ui');


const cli = meow(`
    Usage
      $ ink-app

    Options
        --name  Your name

    Examples
      $ ink-app --name=Jane
      Hello, Jane
`);

render(React.createElement(ui, cli.flags));

ui.js文件内容如下:

const React = require('react');
const { Text, Box, useInput, useStdout, useStdin } = require('ink');
const { useEffect, useState } = React

module.exports = () => (<>
    <Text>I am text</Text>
    <Text bold>I am bold</Text>
    <Text italic>I am italic</Text>
    <Text underline>I am underline</Text>
    <Text strikethrough>I am strikethrough</Text>
    <Text color="green">I am green</Text>
    <Text color="blue" backgroundColor="gray">I am blue on gray</Text>
    <Box borderStyle="single">
        <Text>single</Text>
    </Box>
    <Box borderStyle="double">
        <Text>double</Text>
    </Box>
    <Box borderStyle="round">
        <Text>round</Text>
    </Box>
    <Box borderStyle="bold">
        <Text>bold</Text>
    </Box>
    <Box borderStyle="singleDouble">
        <Text>singleDouble</Text>
    </Box>
    <Box borderStyle="doubleSingle">
        <Text>doubleSingle</Text>
    </Box>
    <Box borderStyle="classic">
        <Text>classic</Text>
    </Box>
</>)

运行

node cli.js

cli.js和ui.js都是nodejs文件格式,ui.js中虽然能使用react等jsx语法,但都在importJsx时进行了转化成js代码.这样运行能正常显示代码索要展示的功能.

打包

➜  ink-app git:(master) ✗ pkg . -t mac -o app
> pkg@5.3.1
> Error! This experimental syntax requires enabling one of the following parser plugin(s): 'jsx, flow, typescript' (5:24)
  /Users/xxxxx/ink-app/ui.js
➜  ink-app git:(master) ✗

使用pkg打包就会发现报错了,查了半天也没找到真正能解决,应该是我这个想法太偏了...

解决

通常我们会用 babel 来将浏览器未兼容的新语法编译为兼容的代码,以便在旧浏览器或者环境下运行。
除了编译运行外,babel 还提供了 @babel/register 来即时编译运行。
使用 @babel/register 的方式很简单,只需要将以下代码放在需要编译运行的代码引入前:require('@babel/register');想将jsx语法转成js,在文件入口头部加上下面这句.

require('@babel/register');

就这一句我加了各种版本的,各种版本不对名字相似终于让我找到了这句正确的....但运行发现还是报错...打包文件找不到

hello world
/snapshot/ink-app/node_modules/yoga-layout-prebuilt/yoga-layout/build/Release/nbind.js:53
        throw ex;
        ^

Error: Cannot find module './ui'

这样的话,就得手动转es6文件后再使用pkg打包转换后的文件.
大致命令如下:

babel *.js -d dist/ && pkg . -t mac -o app

总结

  1. 再cli.js文件头部加上 require('@babel/register');并不能有效解决二进制打包的问题
  2. 先使用babel将文件中使用es6语法的文件都转成满足当前node运行的文件
  3. 再用pkg打包转译后的js文件

放出package.json给大家参考下吧,有些地方都要配置改的.

{
    "name": "ink-app",
    "version": "0.0.0",
    "license": "MIT",
    "bin": "dist/cli.js",
    "engines": {
        "node": ">=10"
    },
    "scripts": {
        "test": "xo && ava",
        "start": "node cli.js",
        "build": "babel *.js -d dist/ && pkg . -t mac -o app"
    },
    "files": [
        "cli.js",
        "ui.js"
    ],
    "pkg": {
        "scripts": [
            "dist/*.js"
        ],
        "assets": [
            "dist/*"
        ]
    },
    "dependencies": {
        "import-jsx": "^4.0.0",
        "ink": "^3.0.9",
        "meow": "^9.0.0",
        "react": "^17.0.2"
    },
    "devDependencies": {
        "@ava/babel": "^2.0.0",
        "@babel/cli": "^7.14.8",
        "@babel/core": "^7.14.8",
        "@babel/preset-env": "^7.14.8",
        "@babel/preset-react": "^7.14.5",
        "@babel/register": "^7.14.5",
        "ava": "^3.15.0",
        "chalk": "^4.1.1",
        "eslint-config-xo-react": "^0.25.0",
        "eslint-plugin-react": "^7.24.0",
        "eslint-plugin-react-hooks": "^4.2.0",
        "ink-testing-library": "^2.1.0",
        "xo": "^0.39.1"
    },
    "ava": {
        "babel": true,
        "require": [
            "@babel/register"
        ]
    },
    "babel": {
        "presets": [
            "@babel/preset-env",
            "@babel/preset-react"
        ]
    },
    "xo": {
        "extends": "xo-react",
        "rules": {
            "react/prop-types": "off"
        }
    }
}

我这半吊子基础不扎实,网上找资料东一块西一块,有个思路也要摸索半天...前端之路路漫漫!

参考文章

在命令行里也能用 React

ink库

nodejs中使用ES6语法(两种方法)

语法转换babel

容易误人的陈旧示例

node pkg 打包一个为一个可执行程序(linux、windows、mac)

通过babel-register在nodejs端使用es6


不悟
44 声望5 粉丝

业余程序员,专业业余