Packaging

Packaging


What is Bundling (Packaging)?

Packaging or bundling in JavaScript combines multiple files and their dependencies for deployment or to publish as a Reusable Library through npm (opens in a new tab), so it can be added to a project using npm i <package-name>, which is the use case for the frontend-components-react library . React apps use popular tools like Webpack,Rollup or Parcel to bundle JavaScript, CSS, images, and fonts. Code splitting can be used to optimize bundle size. Image files can be included in the bundle using file-loader or url-loader.

why Bundling?:

There are several reasons why at PHPCreation organization we need to publish our front-end components as a private package to npm using Github Packages:

  • Reusability: By publishing the front-end components as a package on npm[using Github Registry], all developers within the organization can easily reuse those components in different projects. This can help to save time and effort on development and also ensures that the code is consistent across projects.

  • Maintenance: By creating a private package, the organization can maintain the code in a centralized location. This allows for easier maintenance and updates to the components, without having to manually update each project that uses the code.

  • Security: By using a private package, the organization can ensure that only authorized users have access to the code. This can help to prevent unauthorized use or modification of the code, as well as potential security breaches.

  • Collaboration: Using Github Packages to publish the private package allows for easy collaboration among developers within the organization. They can contribute to the codebase, suggest changes, and submit pull requests, all within the context of the package.

  • Version Control: Using a private package on Github Packages allows for easy version control. we can track changes to the code, roll back to previous versions, and tag specific versions of the package for use in different projects.

Parcel

It is JavaScript module bundler, official website (opens in a new tab)

Parcel.js is an open-source bundler. It supports many popular languages like Typescript and SASS, and can also handle file types like images and fonts. Parcel comes with a few extra tools built-in: a development server, diagnostics, minification, and even image compression. If your project requires additional configurations, you have the option to use Parcel’s many plugins

Parcel main features:

Parcel includes many optimizations designed to reduce bundle sizes, including automatic minification, tree shaking, image optimization, and more.

  • Code spliting : Code splitting involves breaking code into smaller parts, reducing loading time and increasing performance on web applications.


  • Tree shaking : which requires the removal of Unused imports that will decreases dependencies and code size, leading to improved performance, maintenance, and readability.


  • Minification: Parcel includes minifiers for JavaScript, CSS, HTML, and SVG out of the box. Minification reduces the file size of your output bundles by removing whitespace, renaming variables to shorter names, and many other optimizations.

Configuration

We use package.json to configure Parcel.

...
  "source": "src/index.ts",
  "types": "dist/types.d.ts",
  "main": "dist/index.js",
  "alias": {
    "@/*": "./src/$1"
  },
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/",
    "tag": "latest"
  },
  ...
..
  "scripts": {
    ...
    "cleanup": "rm -rf .parcel-cache dist build",
    "build:parcel": "npm run cleanup && parcel build",
    "build:css": " npx tailwindcss -o dist/build.css --minify",
    "build:all": "npm run build:parcel && npm run build:css && mkdir ./build && mv -f ./dist ./build && cp package.json README.md ./build"
   ...
  • cleanup: Deletes the ".parcel-cache", "build folder" and "dist" folders using the "rm" command.

  • build:parcel: Runs the "cleanup" command first, then builds the project using Parcel bundler.

  • build:css: Uses TailwindCSS to generate a minified "build.css" file in the "dist" folder, which is found inside the "build" folder.

  • build:all: Runs all three previous commands sequentially and copies the "package.json" and "README.md" files to the "dist" folder, which is found inside de "build" folder.

Additionally, the alias property is defined to replace @ with the src directory, which can be useful for importing files.

Other important properties include the dependencies and devDependencies properties, which list the packages required to run and develop the application, respectively.

Lastly, the source property specifies the entry point for the application, while the main property indicates the location of the built application's entry point.

Local testing of packages

To test the package locally without publishing it, we have to ...

  • Go into the package directory and type npm run build:all.
  • Go into the output directory into which the build artefact is located, and into which we can find a package.json file (normally into the "build" folder).
  • Type the command npm pack --pack-destination {directory_into_which_to_put_the_local_package}
  • Go into the other project's package.json and edit the dependency to file:{directory_into_which_to_put_the_local_package}

** It's possible that you'll have to run the commands npm cache clean -f and / or npm cache verify

Private publishing to NPM

By default, NPM (Node Package Manager) offers publishing to its public registry, but to publish privately we need to upgrade to their paid plan. Though, we can use Github Packages (opens in a new tab) as the target registry for NPM,this way we don't have to pay for the NPM paid plans.

The package will be under the organization scope, @phpcreation/frontend-components-react. we need to configure our Package.json file to indicate that we will be using Github Packages Registry, add the follwing :

package.json

 
  "publishConfig": {
    "registry": "https://npm.pkg.github.com/",
    "tag": "latest"
  },

Note #1: to publish the package from the using 'npm publish', you will need to authenticate NPM to be using the github Token as password. check out this tutorial (opens in a new tab)

Note #2: Since we are using Semantic-release package to automate to process of publishing and versionning, we need to configure the package.json to use @semantic-release/npm plugin, here is the complete config:

package.json

...
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/github",
"@semantic-release/git",
[
"@semantic-release/npm",
{
"pkgRoot": "dist"
}
]
],
"release": {
"branches": [
"main"
],
"prepare": [
"@semantic-release/npm",
"@semantic-release/changelog",
{
"path": "@semantic-release/git",
"message": "release ${nextRelease.version}\n\n${nextRelease.notes}"
}
]
}
 

Workflow

Using github workflow, we create the workflow file called publish.yml under .github/workflows/:

This GitHub Actions workflow automates the process of publishing a package. It is triggered manually through the GitHub Actions UI. The workflow runs on the latest version of Ubuntu and consists of several steps.

The first step checks out the code using the actions/checkout@v3 action.

The second step caches the node modules using the actions/cache@v3 action, which uses the hashFiles function to generate a unique cache key based on the contents of the package-lock.json file. The cache is stored in the node_modules directory.

The third step sets up Node.js using the actions/setup-node@v1 action. If the cache was hit in the previous step, this step is skipped. Otherwise, it installs the specified version of Node.js and sets the registry URL to https://npm.pkg.github.com/.

The fourth step installs the required packages using npm install. If the cache was hit, this step is skipped.

The fifth step bundles the app using the npm run build:all command.

The final step launches the publishing process using semantic-release, a tool for automating versioning and publishing. It sets the necessary environment variables for authentication and runs the command CI=true npx semantic-release.

Overall, this workflow optimizes the package publishing process by caching node modules, skipping unnecessary steps if the cache is hit, and automating the versioning and publishing process. It helps ensure a consistent and efficient development workflow.

name: Publish package
# for manual action triggering.
on: [workflow_dispatch]
 
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Cache node modules
        id: cache
        uses: actions/cache@v3
        with:
          path: node_modules
          key: cache-node-${{ hashFiles('package-lock.json') }}
      - name: Setup Node
        uses: actions/setup-node@v1
        if: steps.cache.outputs.cache-hit != 'true'
        with:
          node-version: 16
          registry-url: https://npm.pkg.github.com/
      - name: Install packages
        if: steps.cache.outputs.cache-hit != 'true'
        run: npm install
        # bunlde the app
      - name: bundle the package
        run: npm run build:all
      - name: Launch the publishing using semantic release
        env:
          NPM_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
        run: CI=true npx semantic-release

Why not Webpack?

Webpack (opens in a new tab) is a JavaScript module bundler that optimizes, minifies, and bundles multiple modules and their dependencies for the browser.

Webpack also requires detailed configuration and can be difficult to set up and use:

  • Reason #1: it can be time-consuming to configure. Webpack offers many different configuration options, and getting everything set up correctly can be challenging. This is particularly in true our Context since we would be using Webpack for a complex project with many different dependencies and requirements.

  • Reason #2: it requires the use of Babel (opens in a new tab) for transpiling JavaScript code. Babel is a tool that allows developers to write modern JavaScript code that is compatible with older browsers. While Babel is a useful tool, it can add complexity to the development process and may require additional configuration & plugins..

  • Reason #2: Webpack requires the use of plugins to add additional functionality, such as minifying code or generating source maps. While plugins can be very useful, they can also add additional complexity to the development process.

Why not Rollup?

Rollup (opens in a new tab) rollup.js is a JavaScript module bundler, requiring a rollup.config.js file to exist at the root of the project .

While Using Rollup, we faced the following issues:

  • Failed to load some Icons files
  • Failed to properly configure Next.js plugs

Why not Tsup?

Tsup (opens in a new tab) Is a bundler based on esbuil, with 0 config feature.

While Tsup gives a zeroconfig build process, we encoutered the following while trying to use it:

  • Typescript types weren't bundled efficiently.
  • Failed to bundle some Next.js Plugins