TipsAndTricks/NPM: Difference between revisions

From Yocto Project
Jump to navigationJump to search
(Created page with "Node.js packages and applications tend to differ from other applications. They tend to have alot of 'smaller' dependencies and often are not very descriptive of what versions ...")
 
(→‎Yocto 2.1.1 (krogoth) Restrictions: Remove this obsolete section)
 
(76 intermediate revisions by 4 users not shown)
Line 1: Line 1:
Node.js packages and applications tend to differ from other applications. They tend to have alot of 'smaller' dependencies and often are not very descriptive of what versions of these dependencies they require.
== Background ==
JavaScript is becoming a leading programming language for IoT due to the popularity of Node.js  
[https://www.linkedin.com/pulse/top-10-languages-iot-projects-toby-mcclean]
[https://www.quora.com/Which-programming-languages-will-be-most-valuable-in-the-IoT-Internet-of-Things]
[https://blog.jscrambler.com/javascript-the-perfect-language-for-the-internet-of-things-iot].
However Node.js application packages (or modules as they are typically known) tend to have many dependencies and often are not very descriptive of what versions of these dependencies they require. Node.js modules are managed by a tool called Node Package Manager (NPM) which accesses a [https://docs.npmjs.com/misc/registry module registry] to install dependencies. In previous versions of Yocto Node.js module recipes created the package by running npm in the do_compile task that would look something like this
<pre>
SRC_URI = "https://github.com/gruntjs/grunt-cli.git"


Yocto provides a number to tools to help people packaging node modules from NPM (Node Package Manager) and fit within the traditional metadata structure and obey the do_compile/do_fetch rules as well as helping you check your licensing requirements. This post will show you various methods to make package creation easier.
do_compile() {
    # changing the home directory to the working directory, the .npmrc will be created in this directory
    export HOME=${WORKDIR}


== Recipetool ==
    # configure cache to be in working directory
    npm set cache ${WORKDIR}/npm_cache


Recipetool now allows an npm URL to be given like this:
    # clear local cache prior to each compile
    npm cache clear


  recipetool create "npm://registry.npmjs.org;name=grunt-cli;version=1.1.0"
    # compile and install node modules in source directory
    npm --arch=${TARGET_ARCH} --verbose install
}
</pre>


Note that registry.npmjs.org is the default NPM registry but any registry URL can be used. The name and version tags should be self documenting. Behind the scene, recipetool will download each dependency and write a recipe file. The recipe file is fairly simple but will contain every license that recipetool has found and include it in the LIC_FILES_CHKSUM. Note alot of node modules have unclear licensing so "unknown" happens alot in the LICENSE field, have a look at the modules not listed.
The problem with this approach is that the '''npm install''' command triggers download of dependent modules. You have to manually track down the licences of all these component and add them to the recipe. Also due the loose specification of dependent package versioning, building with the same recipe at a later date may result in different package contents. Furthermore, as web operations are not expected in the do_compile task, proxy variables are not propagated so recipes must be extended to add configuration for handling corporate firewalls. In summary although this approach is functional, it's far from ideal.


== Integrity of your package ==
NPM fetcher and recipetool support was added in Yocto 2.1 and improved in 2.2. This simplifies the locking down the packaging of Node.js modules as well as helping you check your licensing requirements.


Recipetool will also create a shrinkwrap and lockdown file for your recipe. Shrinkwrap files in npm are used to make sure that the full dependency chain of a node module is the same as the user expected. Alot of packages don't provide this so we create one on the fly, you can replace it with your own. Lockdown checks that the files recipetool downloaded are the same as the ones your users will download when using your recipe. This simply checks dependencies have not been changed and that your NPM registry is still handing out the same file.
Aspects of these features are not yet documented in the bitbake manual [https://bugzilla.yoctoproject.org/show_bug.cgi?id=10098], so this article will help you get the best out of it.


== Building & dependencies ==
== Creating NPM Packages ==


Some stuff here
Documented on [https://docs.yoctoproject.org/dev-manual/common-tasks.html#creating-node-package-manager-npm-packages the Yocto Project manual]


== Examples ==
=== NPM Projects in Development ===
Although it is useful to package modules already in the registry, adding node.js projects in development is a more common developer use case. The is very similar to the "registry" approach, but instead we give devtool a URL to the source code. To create the cute-files example above from source code we would have done as follows:
devtool add https://github.com/martinaglv/cute-files.git
If you look at the generated recipe, it will be very similar, but the SRC_URI will look like this:
SRC_URI = "git://github.com/martinaglv/cute-files.git;protocol=https \
          npm://registry.npmjs.org;name=commander;version=2.9.0;subdir=node_modules/commander \
          npm://registry.npmjs.org;name=express;version=4.14.0;subdir=node_modules/express \
          npm://registry.npmjs.org;name=content-disposition;version=0.3.0;subdir=node_modules/content-disposition \
          "
You can see that the main module is taken from git repo and dependents obtained from registry. Other than that, recipe is fairly similar and can be built and deployed as above.
 
== Yocto 3.1 (dunfell) update ==
 
The [https://docs.yoctoproject.org/3.2.3/ref-manual/migration-3.1.html#npm-fetcher-changes npm fetch system] has been [https://blog.savoirfairelinux.com/en-ca/2020/dealing-with-angular-npm-dependencies-on-embedded-systems/ refactored] for the [https://www.yoctoproject.org/docs/latest/mega-manual/mega-manual.html#creating-node-package-manager-npm-packages dunfell release].
 
Importing a recipe that uses npm from zeus to dunfell it is not an straightforward process. Syntax and fetch has changed, so you might encounter errors like the following:
 
<pre>
ERROR: cute-files-1.0.2-r0 do_fetch: URL: 'npm://registry.npmjs.org/;name=cute-files;version=1.0.2' is missing the required parameter 'Parameter 'package' required'
</pre>
 
In this case the error is related to the registry syntax. When using the registry url (from devtool or in your recipe) from dunfell release, the package name is specified by the "package" parameter instead of "name". Other errors might occur, so importing npm recipes to dunfell release might require a migration process.
 
=== Migrating NPM recipes ===
 
1. Regenerate the shrinkwrap file
 
As described above, the ''devtool add'' offers the possibility to create recipes and shrinkwrap files for npm applications (either from registry, git, local sources...).
 
You can create an updated shrinkwrap for your application with:
 
<pre>
devtool add <app>
</pre>
 
where ''<app>'' refers to the npm registry, git repository or local sources to your application.
 
Once the command has been successfully executed you can overwrite your previous shrinkwrap with the content of ''build/workspace/recipes/<app>/<app>/npm-shrinkwrap.json''.
 
2. Clean workspace
 
In order to avoid duplicated recipes you should remove the newly generated one:
 
<pre>
rm -rf build/workspace/recipes/<app> build/workspace/appends/<app> build/workspace/sources/<app>
</pre>
 
3. Adapt old recipe
 
* Update ''SRC_URI'' variable: A common use case for npm developers is to integrate a non-registered application that has dependencies to fetch from the registry. As described on the [https://www.yoctoproject.org/docs/3.0.4/mega-manual/mega-manual.html#creating-node-package-manager-npm-packages documentation], until zeus release, this recipe would describe the sources to fetch with the following syntax:
 
<pre>
SRC_URI = "git://github.com/martinaglv/cute-files.git;protocol=https \
              npm://registry.npmjs.org;name=commander;version=2.9.0;subdir=node_modules/commander \
              npm://registry.npmjs.org;name=express;version=4.14.0;subdir=node_modules/express \
              npm://registry.npmjs.org;name=content-disposition;version=0.3.0;subdir=node_modules/content-disposition \
              "
</pre>
 
where the first line represents the repository for the main package and the other three the registry dependencies.
 
However, with the refactoring made on the [https://docs.yoctoproject.org/3.2.3/ref-manual/migration-3.1.html#npm-fetcher-changes dunfell release], the fetcher is capable of getting all the dependencies from a single shrinkwrap file (which is very convenient for large applications). So the ''SRC_URI'' of your recipe can be left as:
 
<pre>
SRC_URI = " \
    git://github.com/tutorialzine/cute-files.git;protocol=https \
    npmsw://${THISDIR}/${BPN}/npm-shrinkwrap.json \
    "
</pre>
 
Note that the syntax uses ''npmsw://'' instead of ''npm://''.
 
* Remove obsolete variables: Another change that might be necessary is to remove the obsolete variables ''NPM_SHRINKWRAP'' and ''NPM_LOCKDOWN'' from your recipe.
 
<pre>
NPM_SHRINKWRAP := "${THISDIR}/${PN}/npm-shrinkwrap.json"
NPM_LOCKDOWN := "${THISDIR}/${PN}/package-lock.json"
</pre>
 
* Remove ''package-lock.json'': The file ''package-lock.json'' can also be removed from your recipe folder.

Latest revision as of 13:27, 22 June 2022

Background

JavaScript is becoming a leading programming language for IoT due to the popularity of Node.js [1] [2] [3]. However Node.js application packages (or modules as they are typically known) tend to have many dependencies and often are not very descriptive of what versions of these dependencies they require. Node.js modules are managed by a tool called Node Package Manager (NPM) which accesses a module registry to install dependencies. In previous versions of Yocto Node.js module recipes created the package by running npm in the do_compile task that would look something like this

SRC_URI = "https://github.com/gruntjs/grunt-cli.git"

do_compile() {
    # changing the home directory to the working directory, the .npmrc will be created in this directory
    export HOME=${WORKDIR}

    # configure cache to be in working directory
    npm set cache ${WORKDIR}/npm_cache

    # clear local cache prior to each compile
    npm cache clear

    # compile and install node modules in source directory
    npm --arch=${TARGET_ARCH} --verbose install
}

The problem with this approach is that the npm install command triggers download of dependent modules. You have to manually track down the licences of all these component and add them to the recipe. Also due the loose specification of dependent package versioning, building with the same recipe at a later date may result in different package contents. Furthermore, as web operations are not expected in the do_compile task, proxy variables are not propagated so recipes must be extended to add configuration for handling corporate firewalls. In summary although this approach is functional, it's far from ideal.

NPM fetcher and recipetool support was added in Yocto 2.1 and improved in 2.2. This simplifies the locking down the packaging of Node.js modules as well as helping you check your licensing requirements.

Aspects of these features are not yet documented in the bitbake manual [4], so this article will help you get the best out of it.

Creating NPM Packages

Documented on the Yocto Project manual

NPM Projects in Development

Although it is useful to package modules already in the registry, adding node.js projects in development is a more common developer use case. The is very similar to the "registry" approach, but instead we give devtool a URL to the source code. To create the cute-files example above from source code we would have done as follows:

devtool add https://github.com/martinaglv/cute-files.git

If you look at the generated recipe, it will be very similar, but the SRC_URI will look like this:

SRC_URI = "git://github.com/martinaglv/cute-files.git;protocol=https \
          npm://registry.npmjs.org;name=commander;version=2.9.0;subdir=node_modules/commander \
          npm://registry.npmjs.org;name=express;version=4.14.0;subdir=node_modules/express \
          npm://registry.npmjs.org;name=content-disposition;version=0.3.0;subdir=node_modules/content-disposition \
          "

You can see that the main module is taken from git repo and dependents obtained from registry. Other than that, recipe is fairly similar and can be built and deployed as above.

Yocto 3.1 (dunfell) update

The npm fetch system has been refactored for the dunfell release.

Importing a recipe that uses npm from zeus to dunfell it is not an straightforward process. Syntax and fetch has changed, so you might encounter errors like the following:

ERROR: cute-files-1.0.2-r0 do_fetch: URL: 'npm://registry.npmjs.org/;name=cute-files;version=1.0.2' is missing the required parameter 'Parameter 'package' required'

In this case the error is related to the registry syntax. When using the registry url (from devtool or in your recipe) from dunfell release, the package name is specified by the "package" parameter instead of "name". Other errors might occur, so importing npm recipes to dunfell release might require a migration process.

Migrating NPM recipes

1. Regenerate the shrinkwrap file

As described above, the devtool add offers the possibility to create recipes and shrinkwrap files for npm applications (either from registry, git, local sources...).

You can create an updated shrinkwrap for your application with:

devtool add <app>

where <app> refers to the npm registry, git repository or local sources to your application.

Once the command has been successfully executed you can overwrite your previous shrinkwrap with the content of build/workspace/recipes/<app>/<app>/npm-shrinkwrap.json.

2. Clean workspace

In order to avoid duplicated recipes you should remove the newly generated one:

rm -rf build/workspace/recipes/<app> build/workspace/appends/<app> build/workspace/sources/<app>

3. Adapt old recipe

  • Update SRC_URI variable: A common use case for npm developers is to integrate a non-registered application that has dependencies to fetch from the registry. As described on the documentation, until zeus release, this recipe would describe the sources to fetch with the following syntax:
 SRC_URI = "git://github.com/martinaglv/cute-files.git;protocol=https \
               npm://registry.npmjs.org;name=commander;version=2.9.0;subdir=node_modules/commander \
               npm://registry.npmjs.org;name=express;version=4.14.0;subdir=node_modules/express \
               npm://registry.npmjs.org;name=content-disposition;version=0.3.0;subdir=node_modules/content-disposition \
               "

where the first line represents the repository for the main package and the other three the registry dependencies.

However, with the refactoring made on the dunfell release, the fetcher is capable of getting all the dependencies from a single shrinkwrap file (which is very convenient for large applications). So the SRC_URI of your recipe can be left as:

SRC_URI = " \
    git://github.com/tutorialzine/cute-files.git;protocol=https \
    npmsw://${THISDIR}/${BPN}/npm-shrinkwrap.json \
    " 

Note that the syntax uses npmsw:// instead of npm://.

  • Remove obsolete variables: Another change that might be necessary is to remove the obsolete variables NPM_SHRINKWRAP and NPM_LOCKDOWN from your recipe.
NPM_SHRINKWRAP := "${THISDIR}/${PN}/npm-shrinkwrap.json"
NPM_LOCKDOWN := "${THISDIR}/${PN}/package-lock.json"
  • Remove package-lock.json: The file package-lock.json can also be removed from your recipe folder.