Demand background
A nodeJS service developed with egg.js needs to be deployed to the customer site. According to the normal deployment operation, nodeJS development environment needs to be installed on the server, then proxy and mirror source are configured, dependencies are installed, and the service is started by NPM start command.
In actual business, the server is operated and maintained by the customer. This complex operation process is obviously not acceptable. What the user needs is an executable EXE file, which can automatically start the service by double-clicking.
The NodeJs runtime environment, NPM dependencies, and source code are all packaged together.
EncloseJS ‘writers strongly recommend users switch to PKG.
Dear users of EncloseJS. I highly encourage you to switch to github.com/zeit/pkg.
To open.
Initialize the Egg project
Egg’s official documentation goes into great detail, starting with a project that follows the instructions. If you’re already familiar with the Egg project, skip this section.
npm init egg --type=simpleCopy the code
If you encounter template download timeout, try adding proxy parameters to the initialization command line:
npm init egg --type=simple -r https://registry.npm.taobao.org/Copy the code
After execution, the project directory structure is as follows:
An egg - project ├ ─ ─ package. Json ├ ─ ─ app | ├ ─ ─ the router. The js │ ├ ─ ─ controller │ | └ ─ ─ home. Js ├ ─ ─ the config | ├ ─ ─ plugin. Js | ├ ─ ─ The config. Default. Js └ ─ ─test└ ─ ─ controller └ ─ ─ home. Test. JsCopy the code
NPM install install project dependencies:
npm installCopy the code
To ensure that the service starts properly, you also need to install egg-bin and egg-scripts globally:
npm install -g egg-bin egg-scriptsCopy the code
At this point, execute the NPM run dev script to start the server in development mode:
npm run devCopy the code
Then open the browser to port localhost:7001 and display hi, egg, and you have debugged the egg service environment.
Began to hit the pit
First install PKG globally so that PKG commands can be executed on the command line:
npm install -g pkgCopy the code
According to the PKG README, we can specify project entry files in three ways:
The entrypoint of your project is a mandatory CLI argument. It may be:
- Path to entry file. Suppose it is
/path/app.js
, then packaged app will work the same way asnode /path/app.js
- Path to
package.json
.Pkg
will followbin
property of the specifiedpackage.json
and use it as entry file.- Path to directory.
Pkg
will look forpackage.json
in the specified directory. See above.
Translation to the translator:
- If you specify an entry file explicitly, such as /path/app.js, PKG will behave like node /path/app.js.
- Specify package.json and PKG will use the bin property of package.json as the entry file.
- Specify a directory where PKG will use the bin property of package.json as the entry file.
In the express project root directory, there is an entry file called app.js. Where is the entry file?
It was hidden by egg.
Following the example of using pm2 to start an Egg application, we can add server.js to the root directory as the entry file.
// server.js
const egg = require('egg');
const workers = Number(process.argv[2] || require('os').cpus().length);
egg.startCluster({
workers,
baseDir: __dirname,
});Copy the code
Also specify the entry file in package.json
{... +"bin": "server.js". }Copy the code
How is PKG packaged?
The PKG iterates through the entire project dependencies starting with the user-specified entry file, and when the javascript file is imported, the PKG compiles it into v8 bytecode and packages it into the executable (Scripts). When importing a static resource, the PKG copies the original content directly into the executable.
Will all the JS files and static resources PKG handle it for you?
No, it depends on how the resource is loaded. PKG automatically packages resources with paths specified by __dirname and __filename into executables, such as the require method (which internally uses __direname to locate resources) and via path.join(__dirname, “.. /path/to/asset”)
require('./router.js')
path.join(__dirname, './app/public/index.html')Copy the code
However, PKG is not recognized and automatically introduced for resource introduction of non-literal parameters, such as:
require('./build/' + cmd + '.js')
path.join(__dirname, 'views/' + viewName)Copy the code
At this point, we need to actively display the resources declared to be packaged in the package.json PKG property. In egg’s project, we first follow the following configuration, and we will adjust when we encounter pits:
// package.json
{
...
"pkg": {
"scripts": [
"./app/**/*.js"."./config/*.js"]."assets": [
"./app/public/**/*"]}... }Copy the code
For example, require(“./server.js”) is written in the code, but the current file directory only has one executable. How does the program run without server.js?
Here comes the PKG file resource loading mechanism.
The PKG modifies node’s FS API to intercept file operations and proxy them into its own virtual snapshot filesystem. For example, at runtime, the PKG will be propped to the /snapshot/server.js path (c:\snapshot on Windows) for server.js loads in require(“./server.js”). And returns the previously packed file contents from the virtual file snapshot system.
What types of file reads and writes hit the PKG virtual file snapshot system? The authors present the following table:
value | with node |
packaged |
__filename | /project/app.js | /snapshot/project/app.js |
__dirname | /project | /snapshot/project |
process.cwd() | /project | /deploy |
process.execPath | /usr/bin/nodejs | /deploy/app-x64 |
process.argv[0] | /usr/bin/nodejs | /deploy/app-x64 |
process.argv[1] | /project/app.js | /snapshot/project/app.js |
process.pkg.entrypoint | undefined | /snapshot/project/app.js |
process.pkg.defaultEntrypoint | undefined | /snapshot/project/app.js |
require.main.filename | /project/app.js | /snapshot/project/app.js |
That is, if you want resources to be automatically packaged into an executable, use path locators such as __filename and __direname. On the other hand, if you want to access a real file system, such as loading environment variable files, you should use path locators such as process.cwd(), which makes it possible to dynamically change the startup or running parameters of the service, such as changing the service port number.
At this point, let’s consider a question: how to handle a situation like local run logging?
Yes, we need to specify the log path through process.cwd() in the Egg configuration file to perform a write to the real file system. Similarly, all reads and writes to the real file system are declared in the configuration file through process.cwd() :
// config/config.default.js
const path = require("path"); module.exports = appInfo => { ... // Config.rundir = process.cwd() +'/run'; Logger = {dir: path.join(process.cwd(),'logs'),}; // Configure the static resource path config.static = {prefix:'/',
dir: process.cwd() + '/public'}; . }Copy the code
When executing the package command, we need to add the –targets option to specify the target format of the package: [nodeRange]-[platform]-[arch], where:
- NodeRange: Specifies the node version, such as node${n} or latest
- Platform: Specifies the operating system, such as FreeBSD, Linux, Alpine, MacOS, and Win
- Arch: Specifies CPU architectures such as X64, x86, ARMV6, armV7
For example, node12-WIN-x64, multiple packaging target formats can be separated by commas.
You can also specify the targets option in the package.json PKG property:
// package.json
{
"pkg": {
"scripts": [
"./app/**/*.js"."./config/*.js"]."assets": [
"./app/public/**/*"]."targets": [
"node12-win-x64"]}}Copy the code
At the same time, to facilitate packaging, we create a new NPM packaging script:
// package.json
{
"scripts": {
"build": "pkg . --out-path D:/ --debug"}}Copy the code
Note, specially through – out – the path parameter specifies the path and the current project is not the same as the output directory, is to ensure that packaging of executable file is also available from the current project environment, avoid some resources while no package to come in, but an executable file can still access from the actual file system, It helps to expose problems early.
At this point, we execute the following command to make an initial packaging attempt:
npm run buildCopy the code
This is where the first problem occurs: Node binaries fail to download due to Intranet proxy or other network reasons. Refer to Issues #419, you can manually download the node binary file from the PKg-fetch Release page and place it in the C:\Users\ user name \. Pkg-cache \v2.6\ directory and continue with the NPM run build command. Finally packaged:
The following error occurs when the executable file is started:
Take a look at the error message:
Error: [egg-core] load file: D:\snapshot\egg-pkg-template\node_modules\egg-security\app\extend\ context.js, error: Cannot find module ‘nanoid/non-secure’
This essentially means that one of the files in the egg-Security package was not packaged into our executable, causing the file to be lost at runtime.
We can explicitly package missing JS resources in PKG scripts:
// package.json
{
"pkg": {
"scripts": [
"./app/**/*.js"."./config/*.js",
+ "./node_modules/nanoid/**/*.js"]."assets": [
"./app/public/**/*"]."targets": [
"node12-win-x64"]}}Copy the code
Execute the package command again, then run the executable:
At this time, although the service started successfully, but there is still an error, we then analyze the error log:
2020-07-28 23:11:43,493 ERROR 37628 nodejs.ENOENTError: ENOENT: no such file or directory, watch ‘D:\snapshot\egg-pkg-template\app’
at FSWatcher.start (internal/fs/watchers.js:169:26)
at Object.watch (fs.js:1415:11)
at Walk.<anonymous> (D:\snapshot\egg-pkg-template\node_modules\wt\index.js:183:20)
at Walk.emit (events.js:315:20)
at D:\snapshot\egg-pkg-template\node_modules\ndir\lib\ndir.js:107:16
at zalgoSafe (pkg/prelude/bootstrap.js:260:10)
at pkg/prelude/bootstrap.js:962:9
at pkg/prelude/bootstrap.js:336:5
at pkg/prelude/bootstrap.js:314:14
at FSReqCallback.wrapper [as oncomplete] (fs.js:516:5)
We need to add env to the config directory to specify that the current environment is production. Also, remember to modify the PKG configuration to ensure that the file is packaged.
// package.json
{
"pkg": {
"scripts": [
"./app/**/*.js",
- "./config/*.js",
+ "./config/*"."./node_modules/nanoid/**/*.js"]."assets": [
"./app/public/**/*"]."targets": [
"node12-win-x64"]}}Copy the code
Execute the package command again and run the executable program, starting smoothly:
At this time, the localhost:7001 port is opened, and the Egg service runs normally. At the same time, the log, RUN and public folders are added in the current directory, which confirms the configuration written by the real file system.
This is just a primer, the subsequent business code development is left to everyone to play. This project has been uploaded to github Coodool/ egg-pkG-template.
The resources
- Package node.js projects (the Egg framework) as executable packages using PKG
- Online deployment of egg.js – Package egg.js project with PKG
- Package Node.js applications using PKG
- github vercel/pkg
- Egg Official Document