2021年从零开发前端项目指南

1,483 阅读8分钟

之前翻译过一篇 前端工程化发展历史 的文章,WebpackBabelEslint 现在基本上就是前端项目的标配了。

但工作以后一般很少接触这些配置,都是在前人配置好的基础上去写业务代码。即使有机会从零配置一个项目,一般也不会自己手动建这些配置文件,直接用 create-react-appAnt Design Pro 等自动帮我们生成各个目录和配置文件就可以了,省时省力。

这篇文章的话就从零手动去配置一个前端项目,会涉及到 WebpackReactBabelTypeScriptAnt DesignSassEslintPrettier,本文的话就本着「不求甚解」的态度,主要过一下各个模块的使用,适合从零一步一步跟着操作。

前端工程化项目是建立在 node.js 环境下的,之后需要安装各个 npm 包,所以首先电脑必须已经配置好了 node 环境。

新建一个目录然后执行 npm init 来初始化一个项目。

npm init

然后一路回车就可以,只是生成了 package.json 文件,后续想改的话也能改。

img

Webpack

前端不断发展,但很多特性浏览器不一定会支持,ES6 模块,CommonJs 模块、Scss/lessjsx 等等,通过 Webpack 我们可以将所有文件进行打包、压缩混淆,最终转换为浏览器识别的代码。

除了安装 Webpack ,我们需要安装对应的命令行工具 webpack-cli,以及实现了热加载,也就是自动监听我们文件变化然后刷新网页的 webpack-dev-server

由于这些工具只在开发阶段使用,所以我们安装的时候可以加上 -D(--save-dev) 命令,这样开发环境就不会打包了。

npm i -D webpack webpack-cli webpack-dev-server

安装之后 package.json 会自动记录我们安装的 node 包,对应版本如下,如果安装的和我不一样的话,后边的一些配置可能略有不同。

{
  ...
  "devDependencies": {
    "webpack": "^5.51.1",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.0.0"
  }
}

接下来在根目录新建 webpack.config.js 进行项目的配置,主要配置入口文件,打包输目录,以及 devServer 的目录。

const path = require('path')
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  },
  devServer: {
    static: path.resolve(__dirname, './dist')
  }
}

新建一下上边相应的文件。

main.js 文件主要实现在网页写 hello world

// /src/main.js
document.write('hello world')

新建 dist 目录,在里边新建 index.html 文件,引入 <script src="bundle.js"></script>

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>前端工程化</title>
  </head>
  <body>
    <div id="app" />
    <script src="bundle.js"></script>
  </body>
</html>

最后在 package.json 新建两条命令,默认的 test 命令可以直接删掉了。

...
"scripts": {
    "dev": "webpack-dev-server --mode development --open",
    "build": "webpack --mode production"
},
...

执行 npm run dev ,此时会自动打开 http://localhost:8080/

img

React

React 可以让我们专注于构建用户界面,而不需要再手动维护 dom 元素的更新,当然还可以用 VUE

安装核心库 react ,以及渲染 Webreact-dom

npm i react react-dom

修改 src/main.js 体验一下。

// /src/main.js
import React from 'react';
import ReactDOM from 'react-dom';

class Hello extends React.Component {
    render() {
        return React.createElement('div', null, `Hello ${this.props.toWhat}`);
    }
}

ReactDOM.render(
    React.createElement(Hello, { toWhat: 'World by React' }, null),
    document.getElementById('app')
);

npm run dev 看下效果:

img

这里会发现上边都调用了 React.createElement 来创建元素,如果页面复杂的的话,那一层套一层就太繁琐了,React 为我们提供了 JSX 语法来简化写法。

让我们改写一下:

// /src/main.js
import React from 'react';
import ReactDOM from 'react-dom';

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}

ReactDOM.render(
  <Hello toWhat="World by jsx" />,
  document.getElementById('app')
);

但此时会发现项目跑不起来了

img

现在,我们就需要 Babel 了。

Babel

babel 可以为我们把各种语法、新功能转换为浏览器所能识别的 js 。这里我们先安装一下 babel 以及在 webpack 中使用的 babel-loader

npm i -D @babel/core babel-loader

然后在 webpack 中引入 babel-loader ,用来对 js 进行转换,更改 webpack.config.js 文件。

const path = require('path')
module.exports = {
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.(js)x?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
    ],
  },
  devServer: {
    static: path.resolve(__dirname, './dist')
  }
}

然后我们来安装 @babel/preset-react 来转换 jsx 语法。

npm i -D @babel/preset-react

在根目录新建 babel 的配置文件 babel.config.json

// babel.config.json
{
    "presets": [
        "@babel/preset-react"
    ]
}

此时再运行 npm run dev 就发现项目成功跑起来了!

然后我们还可以安装一些其他 babel 以便使用最新的 ES 语法,比如箭头函数、async await、问号表达式等等, 需要什么就可以配置什么。当浏览器不支持这些特性时,babel 可以帮我们实现 polyfill 进行降级。

@babel/preset-env 包含了许多 ES 的新特性,core-js 实现 ployfill,通过这两个 babel 各种 ES 最新的特性就都可以放心使用了,如果有不满足的我们可以单独配置 babel 的插件。

npm i -D @babel/preset-env core-js

然后我们再修改下 babel 的配置文件。

// babel.config.json
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage",
                "corejs": 3
            }
        ],
        "@babel/preset-react"
    ],
    "plugins": [
    ]
}

其中 useBuiltIns": "usage" 代表自动判断每个文件是否引入 ployfillcorejs: 3 是指定版本。

TypeScript

越来越多的项目引入了 TypeScript ,尤其是规模比较大的项目,通过 ts 可以让一些 bug 提前暴露,平时自己开发的话也可以引入 ts,提前了解学习。

项目引入 ts 的话有两种方式:

  1. 使用 TypeScript Compiler (TSC)ts 编译为 ES5 以便能够在浏览器中运行。并且使用 TSC 进行类型检查。
  2. 使用 Babel 翻译 TS,使用 TSC 进行类型检查。

这里的话使用第二种方式,让 BabelTSC 各司其职。

首先安装 TypeScript 以及 Reacttype

npm i -D typescript @types/react @types/react-dom

根目录新建 tsconfig.json 进行 ts 的配置。

// tsconfig.json
{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "lib": [
            "dom"
        ],
        "jsx": "react",
        "noEmit": true,
        "sourceMap": true,
        /* Strict Type-Checking Options */
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
    },
    "include": [
        "src"
    ]
}

"noEmit": true, 表明 ts 只做类型检查,不进行编译输出。

然后我们将 src/main.js 修改为 src/main.tsx,并且加上类型。

// /src/main.js
import * as React from 'react';
import * as ReactDOM from 'react-dom';

type Props = {
  toWhat: string;
};
type State = {
 
};

class Hello extends React.Component<Props, State>  {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}

ReactDOM.render(
  <Hello toWhat="World by jsx" />,
  document.getElementById('app')
);

接下来进行 babel 的配置,安装 @babel/preset-typescript,将我们代码从 ts 转为 js

npm i -D @babel/preset-typescript

babel 配置文件中加入。

// babel.config.json
{
    "presets": [
        "@babel/preset-typescript",
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage",
                "corejs": 3
            }
        ],
        "@babel/preset-react"
    ],
    "plugins": [
    ]
}

最后在 webpack.config.jsbabel 匹配的路径中加入 tsx

const path = require('path')
module.exports = {
  entry: './src/main.tsx',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.(js|ts)x?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
    ],
  },
  resolve: {
    // 引入模块的时候可以省略这些后缀
    extensions: ['.tsx', '.ts', '.jsx', '.js'],
  },
  devServer: {
    static: path.resolve(__dirname, './dist')
  }
}

我们可以全局安装一下 typescript ,便于使用 tsc 命令进行类型检查。

npm install -g typescript

可以运行一下 tsc -w 实时进行类型检查。

img

Ant Design

引入组件库,方便更快的开发。

npm install antd

顺便可以按照习惯把 main.tsx 中的 hello 组件抽离出来并且命名为 app.tsx

// /src/App.tsx
import * as React from 'react';
import { DatePicker } from 'antd';

type Props = {
    toWhat: string;
};
type State = {

};

class App extends React.Component<Props, State>  {
    render(): JSX.Element {
        return <div>
            Hello {this.props.toWhat}
            <div>
                <DatePicker></DatePicker>
            </div>
        </div>;
    }
}

export default App;

然后我们在 main.tsx 引入 antdcss 文件。

// /src/main.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import App from './App'

ReactDOM.render(
  <App toWhat="World by jsx" />,
  document.getElementById('app')
);

此时就需要在 webpack.config.js 配置文件中补上 cssloader ,先安装一下。

npm i -D style-loader css-loader

css-loader 可以让我们在 js 中引入 cssstyle-loader 帮我们将 cssstyle 标签的形式插入到页面。

安装好后进行配置 loader

const path = require('path')
module.exports = {
  entry: './src/main.tsx',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.(js|ts)x?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  resolve: {
    // 引入模块的时候可以省略这些后缀
    extensions: ['.tsx', '.ts', '.jsx', '.js'],
  },
  devServer: {
    static: path.resolve(__dirname, './dist')
  }
}

然后就成功引入日期选择器了。

img

Sass

Sasscss 的预编译器,可以让我们写样式更顺手,具体特性可以参考 官网,我用的最多的就是可以嵌套形式写 css,很方便。

我们安装一下 Sass 以及它的 loader

npm install sass-loader sass  --save-dev

然后在 webpack.config.js 配置一下

const path = require('path');
module.exports = {
  entry: './src/main.tsx',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /\.(js|ts)x?$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          // 将 JS 字符串生成为 style 节点
          'style-loader',
          // 将 CSS 转化成 CommonJS 模块
          'css-loader',
          // 将 Sass 编译成 CSS
          'sass-loader',
        ],
      },
    ],
  },
  resolve: {
    // 引入模块的时候可以省略这些后缀
    extensions: ['.tsx', '.ts', '.jsx', '.js'],
  },
  devServer: {
    static: path.resolve(__dirname, './dist'),
  },
};

App.jsx 加几个类名,引入 App.scss

// /src/App.tsx
import * as React from 'react';
import { DatePicker } from 'antd';
import './App.scss';

type Props = {
  toWhat: string;
};
type State = {};

class App extends React.Component<Props, State> {
  render(): JSX.Element {
    return (
      <div className="app">
        <div className="text">Hello</div>
        <div>{this.props.toWhat}</div>
        <div>
          <DatePicker></DatePicker>
        </div>
      </div>
    );
  }
}

export default App;

新建 App.scss,添加颜色实验一下。

.app {
  .text {
    color: #f00;
  }
}

npm run dev 看下效果

img

Eslint

可以配置 eslint 来进行语法上静态的检查,也可以对 ts 进行检查。

npm i eslint -D

可以全局安装一下 npm i -g npx 命令,能够更方便的运行 node_modules/.bin 目录下的命令.

不然的话我们要执行 eslint 命令的话需要执行 ./node_modules/.bin/eslint --version 才能取到。或者像上边为了执行 tsc 命令,全局安装了 typescript。或者在 package.json 里边添加一个自定义命令。不过还是 npx 是最方便的。

让我们初始化 eslint.

npx eslint --init

然后按照项目需要选择对应的选项,最后自动安装相应的依赖。

img

然后 eslint 就自动为我们生成了 .eslintrc.js 配置文件,顺便补一个 "node": true,不然的话 module.exports 直接报错。

module.exports = {
    "env": {
        "browser": true,
        "es2021": true,
      	"node": true,
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "@typescript-eslint"
    ],
    "rules": {
    }
};

然后我们在 package.json 中可以添加一个 lint 命令来修复代码。

{
  "name": "fe-learn",
  "version": "1.0.0",
  "description": "前端工程化项目学习",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --mode development --open",
    "build": "webpack --mode production",
    "lint": "eslint src --fix"
  },
  "author": "windliang",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.15.0",
    "@babel/preset-env": "^7.15.0",
    "@babel/preset-react": "^7.14.5",
    "@babel/preset-typescript": "^7.15.0",
    "@types/react": "^17.0.19",
    "@types/react-dom": "^17.0.9",
    "@typescript-eslint/eslint-plugin": "^4.29.2",
    "@typescript-eslint/parser": "^4.29.2",
    "babel-loader": "^8.2.2",
    "core-js": "^3.16.2",
    "eslint": "^7.32.0",
    "eslint-plugin-react": "^7.24.0",
    "typescript": "^4.3.5",
    "webpack": "^5.51.1",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.0.0"
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  }
}

然后执行 npm run lint 即可进行 eslint 的相关修复。

配合 Vscode 我们也可以做到边写代码边自动检测 eslint,以及保存的时候自动修复 eslint 相关错误。

可以安装 Eslint 插件,以及在 vscode 的设置中加入以下配置,点击下图的右上角可以直接进行配置的编辑。

img
{
  "eslint.validate": ["javascript", "javascriptreact", "vue", "typescript", "typescriptreact"],
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
}

为了使用更完善的 eslint 配置,我们也可以直接引用腾讯 Alloy 团队的推荐配置,参考 这里

Prettier

prettier 主要做代码风格上的检查,字符串双引号还是单引号?几个空格?类似这样的。

当然 eslint 也可以配置这些,但为了分离它们各自的职责,最好还是用 prettier 来格式化代码风格,先安装一下。

npm i -D prettier  

然后新建一个配置文件 .prettierrc.js,这里直接引用 腾讯 Alloy 团队推荐的配置。

// .prettierrc.js
module.exports = {
  // max 120 characters per line
  printWidth: 120,
  // use 2 spaces for indentation
  tabWidth: 2,
  // use spaces instead of indentations
  useTabs: false,
  // semicolon at the end of the line
  semi: true,
  // use single quotes
  singleQuote: true,
  // object's key is quoted only when necessary
  quoteProps: 'as-needed',
  // use double quotes instead of single quotes in jsx
  jsxSingleQuote: false,
  // no comma at the end
  trailingComma: 'all',
  // spaces are required at the beginning and end of the braces
  bracketSpacing: true,
  // end tag of jsx need to wrap
  jsxBracketSameLine: false,
  // brackets are required for arrow function parameter, even when there is only one parameter
  arrowParens: 'always',
  // format the entire contents of the file
  rangeStart: 0,
  rangeEnd: Infinity,
  // no need to write the beginning @prettier of the file
  requirePragma: false,
  // No need to automatically insert @prettier at the beginning of the file
  insertPragma: false,
  // use default break criteria
  proseWrap: 'preserve',
  // decide whether to break the html according to the display style
  htmlWhitespaceSensitivity: 'css',
  // vue files script and style tags indentation
  vueIndentScriptAndStyle: false,
  // lf for newline
  endOfLine: 'lf',
  // formats quoted code embedded
  embeddedLanguageFormatting: 'auto',
};

同样的,为了保存的时候自动帮我们格式化,我们可以安装 VscodePrettier 插件,以及再修改 Vscode 的配置。

{
  "files.eol": "\n",
  "editor.tabSize": 2,
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "eslint.validate": ["javascript", "javascriptreact", "vue", "typescript", "typescriptreact"],
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

总结

通过上边一系列的操作后,就可以开始愉快的开始写项目了,经验有限,上边有问题的地方还请大家指出。

上边的代码都比较零碎,可以在 github 上看整个代码。

上边每一块都是一个很大的地方,未来的话会继续边学习边总结,欢迎一起交流。