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 tofile:{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