CRA?
Create React App 是一個由 Facebook 開發建構和維護的工具鍊,用來引導 React 應用。可以簡單用一行指令 Create React App 就會設置工具,只需要開啟 React 專案。
CRA 的優點
- 一行簡單的 command
npx create-react-app my-app
- 要學的很少,只要專注在 React 本身,不需要擔心 webpack、babel 或其他 dependencies。
- 只需要建立 dependency: react-scripts,這會維護所有你建立的 dependency,所以可以簡單升級,只要一個 command。
npm install react-scripts@latest
CRA 的缺點
- 較難增加客制的 config,需要增加客制的 config 方式是退出 app,但這和一行命令本身互相抵觸,其他的方法是再安裝 package 像是 customize-cra or react-app-rewired 但是他們有容量限制。
- 因為只有一行指令就可以開發 React app,初學者可能會認為 react-scripts 是跑 React app 的唯一,然後就不會知道編譯的 babel、打包的 webpack,因為這些都被包在 react-scripts 下,這些發生了直到我讀到這篇文章。
- CRA 很肥,CRA 附帶 SASS,如果想用 plain CSS 或 Less,那 SASS 就會是永遠用不到的 dependency,這是一個非 CRA 應用的 package.json。
另一個取代 CRA 的方式是設置自己的樣板。CRA 唯一的好處是只有一行指令,我們可以自己排除所有的缺點來設置 dependencies 和 configs。
這個 repo 有下面要說的內容。
一開始先用 npm 和 git 初始化專案
npm initgit init
建立一個 .gitignore 來忽略以下檔案
node_modulesbuild
現在我們來看要跑一個 react app 需要基本的 dependencies
react 和 react-dom
這兩個要運作
npm install react react-dom --save
轉譯器(Babel)
可以編譯 ES5 以上的程式碼讓舊的瀏覽器也支援,也可用來轉換 JSX。
npm install @babel/core @babel/preset-env @babel/preset-react --save-dev
一個給 React 簡單的 bable config,增加一個 .babelrc 檔案或增加 package.json 一個屬性
新增 .babelrc
{ "presets": [ "@babel/preset-env", "@babel/preset-react" ]}
或加在 package.json
"babel": { "presets": [ "@babel/preset-env", "@babel/preset-react" ]}
打包 (Webpack)
打包是將 dependencies 和所有的 code 變成一個檔案。
npm install webpack webpack-cli webpack-dev-server babel-loader css-loader style-loader html-webpack-plugin --save-dev
一個簡單的 webpack.config.js
const path = require('path');const HtmlWebPackPlugin = require('html-webpack-plugin');module.exports = { output: { path: path.resolve(__dirname, 'build'), filename: 'bundle.js', }, resolve: { modules: [path.join(__dirname, 'src'), 'node_modules'], alias: { react: path.join(__dirname, 'node_modules', 'react'), }, }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', }, }, { test: /\.css$/, use: [ { loader: 'style-loader', }, { loader: 'css-loader', }, ], }, ], }, plugins: [ new HtmlWebPackPlugin({ template: './src/index.html', }), ],};
有需要也可以增加 loader,這篇文章有介紹不同的 webpack configs。
這裡有我們需要的 dependencies 了,來增加一個 HTML template 和一個 react component。
來建立一個 src 資料夾增加一個 index.html 檔:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <title>React Boilerplate</title> </head> <body> <div id="root"></div> </body></html>
建立一個 HelloWorld.js 的 react component 到 src 資料夾。
import React from 'react';const HelloWorld = () => { return ( <h3>Hello World</h3> );};
export default HelloWorld;
增加一個 index.js 檔案到 src 資料夾。
import React from 'react';import { render } from 'react-dom';import HelloWorld from './HelloWorld';render(<HelloWorld />, document.getElementById('root'));
最後,在 package.json 增加 start 和 build scripts:
"scripts": { "start": "webpack server --mode=development --open --hot", "build": "webpack --mode=production"}
這樣 react app 就可以執行了,試著下 command 指令:npm start 和 npm run build。
實作和 CRA 一樣用一行指令就可以開始開發。
當我們在 command line 打一個特定的指令的時候(樣板名稱)我們將用一個可以執行的 JS 檔案,例如:reactjs-boilerplate new-project。我們將用 package.json 中 bin 的屬性。
要可以執行 JS 檔案,先安裝 fs-extra
npm i fs-extra
建立 bin/start.js 檔案到專案的根目錄,裡面內容:
#!/usr/bin/env node
const fs = require("fs-extra");
const path = require("path");
const https = require("https");
const { exec } = require("child_process");const packageJson = require("../package.json");const scripts = `"start": "webpack server --mode=development --open --hot", "build": "webpack --mode=production"`;const babel = `"babel": ${JSON.stringify(packageJson.babel)}`;const getDeps = (deps) =>
Object.entries(deps)
.map((dep) => `${dep[0]}@${dep[1]}`)
.toString() .replace(/,/g, " ")
.replace(/^/g, "")
// exclude the dependency only used in this file, nor relevant to the boilerplate
.replace(/fs-extra[^\s]+/g, "");console.log("Initializing project..");// create folder and initialize npm
exec(
`mkdir ${process.argv[2]} && cd ${process.argv[2]} && npm init -f`,
(initErr, initStdout, initStderr) => {
if (initErr) {
console.error(`Everything was fine, then it wasn't:
${initErr}`);
return;
}
const packageJSON = `${process.argv[2]}/package.json`;
// replace the default scripts
fs.readFile(packageJSON, (err, file) => {
if (err) throw err;
const data = file
.toString()
.replace(
'"test": "echo \\"Error: no test specified\\" && exit 1"',
scripts )
.replace('"keywords": []', babel);
fs.writeFile(packageJSON, data, (err2) => err2 || true);
}); const filesToCopy = ["webpack.config.js"]; for (let i = 0; i < filesToCopy.length; i += 1) {
fs.createReadStream(path.join(__dirname, `../${filesToCopy[i]}`)).pipe(
fs.createWriteStream(`${process.argv[2]}/${filesToCopy[i]}`)
);
} // npm will remove the .gitignore file when the package is installed, therefore it cannot be copied, locally and needs to be downloaded. Use your raw .gitignore once you pushed your code to GitHub.
https.get(
"https://github.com/youarebju/minimal-reactjs-boilerplate/blob/main/.gitignore",
(res) => {
res.setEncoding("utf8");
let body = "";
res.on("data", (data) => {
body += data;
});
res.on("end", () => {
fs.writeFile(
`${process.argv[2]}/.gitignore`,
body,
{ encoding: "utf-8" },
(err) => {
if (err) throw err;
}
);
});
}
); console.log("npm init -- done\n"); // installing dependencies console.log("Installing deps -- it might take a few minutes..");
const devDeps = getDeps(packageJson.devDependencies);
const deps = getDeps(packageJson.dependencies);
exec(
`cd ${process.argv[2]} && git init && node -v && npm -v && npm i -D ${devDeps} && npm i -S ${deps}`,
(npmErr, npmStdout, npmStderr) => {
if (npmErr) {
console.error(`Some error while installing dependencies ${npmErr}`);
return;
}
console.log(npmStdout);
console.log("Dependencies installed"); console.log("Copying additional files..");
// copy additional source files
fs.copy(path.join(__dirname, "../src"), `${process.argv[2]}/src`)
.then(() =>
console.log(
`All done!\n\nYour project is now ready\n\nUse the below command to run the app.\n\ncd ${process.argv[2]}\nnpm start`
)
)
.catch((err) => console.error(err));
}
);
}
);
將可以執行的 JS 檔案和指令連起來,貼這行到 package.json
"bin": { "your-boilerplate-name": "./bin/start.js"}
設定在本機連結 package
npm link
現在可以在 terminal 執行 your-boilerplate-name my-app,我們的 start.js 就會執行建立一個新的 my-app 資料夾,會複製 package.json、webpack.config.js、gitignore、src 然後安裝 dependencies 在 my-app 專案裡。
這樣製作的 React 專案就可以用一行指令就完成了。
還可以一步驟發佈在 npm registry。
首先 commit 和 push 你的程式碼到 GitHub 然後遵循這些指令。
我們幾分鐘就做了一個可以取代 create-react-app 的東西,不肥大,可以增加自己的 dependencies,簡單增加、修改 configs。
這個設定非常小,還不能用在產品,還必須增加更多 webpack configs 來優化建立的東西。
回顧
- 了解 CRA 的優點和缺點。
- 用 CRA 單一命令的優點到 project 中。
- 增加了最迷你的 webpack 和 babel configs 去跑一個 react 應用。
- 建立了一個 HelloWorld.js react component 然後運行和 build。
- 建立了一個可以執行的 JS,對應到 command 名稱,透過 package.json 的 bin 屬性。
- 用 npm link 來連結樣板,用一行指令建立樣板到新的 react 專案中。