Referencing mini-webpack, I implemented a simple webpack from scratch using Rust. This allowed me to gain a deeper understanding of webpack and also improve my Rust skills. It's a win-win situation!
Code repository: https://github.com/ParadeTo/rs-webpack
This article corresponds to the Pull Request: https://github.com/ParadeTo/rs-webpack/pull/4
The title seems unrelated to this series, as implementing webpack with Rust involves Node.js plugin development. But don't worry, let me explain. When we use webpack for bundling, don't we often run the following command?
webpack --config webpack.config.js
Similarly, we want our RS Webpack to support such a command. But how can we get the content exported from webpack.config.js
in Rust? One solution is to have a JS runtime to execute webpack.config.js
. However, this approach seems a bit heavy, and it requires the JS runtime to be able to recreate the content of webpack.config.js
in Rust. After searching for tools, I couldn't find anything suitable, so I had to take a different approach.
By examining the source code of Rspack, I found that it uses NAPI-RS to develop Node.js plugins. The specific approach is to write the core code of the packer using Rust, compile it into a plugin using NAPI-RS for use in Node.js, and let Node.js handle the import and parsing of the configuration file, passing it as a parameter to the interface provided by Rust.
To learn how to use NAPI-RS, you can refer to the official website. This article mainly explains how to transform our project into the desired result.
First, let's change our project structure as follows:
.
├── Cargo.lock
├── Cargo.toml
├── crates // Rust crates
│ ├── rswebpack_binding // Generated by NAPI
│ └── rswebpack_core
├── packages // JS packages
│ └── rswebpack-cli
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── readme.md
- Under
crates
, we have Rust projects.rswebpack_binding
is generated by NAPI and is mainly used for exporting interfaces.rswebpack_core
is the core library, where we moved the relevant code from the previous article. - Under
packages
, we have JS projects.rswebpack-cli
will eventually be published as a command-line tool.
In the rswebpack_binding
crate, the code is relatively simple. It just wraps the original Compiler
:
// lib.rs
#![deny(clippy::all)]
use napi::Result;
use raw_config::RawConfig;
use rs_webpack_core::compiler::Compiler;
#[macro_use]
extern crate napi_derive;
mod raw_config;
#[napi]
pub struct RsWebpack {
compiler: Box<Compiler>,
}
#[napi]
impl RsWebpack {
#[napi(constructor)]
pub fn new(raw_config: RawConfig) -> Result<Self> {
let config = raw_config.try_into().expect("Config transform error");
Ok(Self {
compiler: Box::new(Compiler::new(config)),
})
}
#[napi]
pub fn run(&mut self) {
self.compiler.as_mut().run();
}
}
// raw_config.rs
use rswebpack_core::config::{Config, Output};
#[napi(object)]
pub struct RawOutput {
pub path: String,
pub filename: String,
}
impl TryFrom<RawOutput> for Output {
type Error = ();
fn try_from(value: RawOutput) -> Result<Self, Self::Error> {
Ok(Output {
path: value.path.into(),
filename: value.filename.into(),
})
}
}
#[napi(object)]
pub struct RawConfig {
pub root: String,
pub entry: String,
pub output: RawOutput,
}
impl TryFrom<RawConfig> for Config {
type Error = ();
fn try_from(value: RawConfig) -> Result<Self, Self::Error> {
Ok(Config {
root: value.root.into(),
entry: value.entry.into(),
output: value.output.try_into()?,
})
}
}
Here, we define RawConfig
to receive the configuration passed from JS, and we also specify how to convert RawConfig
into Config
. However, the conversion rules are currently very simple.
As for rswebpack-cli
, it's even simpler. We just need to parse the command-line arguments, read the configuration, and call the interface exported by the plugin:
#!/usr/bin/env node
const path = require('path')
const {RsWebpack} = require('@rswebpack/binding')
const argv = require('yargs-parser')(process.argv.slice(2))
const config = require(path.resolve(
process.cwd(),
argv.config || 'rswebpack.config.js'
))
const rsWebpack = new RsWebpack(config)
rsWebpack.run()
Don't forget to configure the command name in package.json
:
{
"name": "@rswebpack/cli",
"dependencies": {
"@rswebpack/binding": "workspace:*",
"yargs-parser": "^21.1.1"
},
"bin": {
"rswebpack": "./index.js"
}
}
Then run npm link
. After that, create a new directory:
.
├── const.js
├── index.js
└── rswebpack.config.js
The rswebpack.config.js
file should contain the following:
const path = require('path')
module.exports = {
root: path.resolve(__dirname),
entry: 'index.js',
output: {
path: path.resolve(__dirname, 'out'),
filename: 'bundle.js',
},
}
Finally, run rswebpack --config rswebpack.config.js
. If it outputs bundle.js
correctly, it means the refactor was successful.
Please kindly give me a star!
Top comments (0)