React 0 to 1(1)

58 阅读2分钟

其实主要是想复习webpack

从HTML JavaScript开始

初始化

mkdir react-linkstart && cd $_
git init
pnpm init
echo "node_modules/\ndist/" > .gitignore
touch index.html
mkdir src && cd $_ && echo "console.log('Hello world!')" > index.js
cd .. && mkdir dist

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React Link Start</title>
</head>
<body>
  <h1>Hello world</h1>
</body>
</html>

当前文件结构

.
├── .gitignore
├── index.html
├── package.json
├── dist
└── src
    └── index.js

安装webpack

pnpm i webpack webpack-cli webpack-dev-server html-webpack-plugin -D

package.json

"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "dev": "webpack serve --config ./webpack.config.js"
},

webpack.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  devServer: {
    port: 3000,
    hot: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'index.html'),
      filename: 'index.html',
    })
  ],
}

运行一下pnpm run dev

image.png

配置babel

安装babel

pnpm i babel-loader @babel/cli @babel/core @babel/preset-env -D

创建.babelrc

{
    "presets": ["@babel/preset-env"],
    "plugins": []
}

webpack.config.js改为webpack.dev.js

创建webpack.prod.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const srcPath = path.resolve(__dirname, './src')
const distPath = path.resolve(__dirname, './dist')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  devServer: {
    port: 5000,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'index.html'),
      filename: 'index.html',
    })
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        include: srcPath,
        exclude: /node_modules/
      },
    ]
  },
  output: {
    filename: 'bundle.js',
    path: distPath,
  }
}

修改一下package.json

scripts: {
    "dev": "webpack serve --config ./webpack.dev.js",
    "build": "webpack --config ./webpack.prod.js"
}

运行一下pnpm run build

image.png

拆分webpack配置

先更改一下部分文件结构

mv ./index.html src/index.html
mkdir build && cd $_
touch paths.js webpack.common.js webpack.dev.js webpack.prod.js
# 安装webpack-merge和clean-webpack-plugin
pnpm i webpack-merge clean-webpack-plugin -D

paths.js

const path = require('path')

const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')

module.exports = {
  srcPath,
  distPath
}

webpack.common.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const { srcPath } = require('./paths')

module.exports = {
  entry: path.join(srcPath, 'index'),
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(srcPath, 'index.html'),
      filename: 'index.html',
    })
  ],
}

webpack.dev.js

const webpackCommonConf = require('./webpack.common.js')
const { merge } = require('webpack-merge')

module.exports = merge(webpackCommonConf, {
  mode: 'development',
  devServer: {
    port: 3000,
    hot: true,
  },
})

webpack.prod.js

const webpackCommonConf = require('./webpack.common.js')
const { merge } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const { srcPath, distPath } = require('./paths')

module.exports = merge(webpackCommonConf, {
  mode: 'production',
  output: {
    filename: 'bundle.[contenthash:8].js',
    path: distPath,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        include: srcPath,
        exclude: /node_modules/
      },
    ]
  },
  plugins: [
    new CleanWebpackPlugin(), // build时默认清空output.path文件夹
  ],
})

处理图片等静态资源(并没有等)

安装依赖

pnpm i file-loader url-loader -D

webpack.dev.js

module: {
  rules: [
    // 直接引入图片url
    {
      test: /\.(png|jpg|jpeg|gif)$/,
      use: 'file-loader',
    }
  ]
}

webpack.prod.js

module: {
  rules: [
    {
      test: /\.(png|jpg|jpeg|gif)$/,
      use: {
        loader: 'url-loader',
        // 小于5kb的图片用base64, 否则仍然使用file-loader
        options: {
          limit: 5 * 1024,
          fallback: {
            loader: 'file-loader',
            options: {
              name: 'img/[name].[hash:8].[ext]'
            }
          }
        }
      }
    }
  }
}

css方案

pnpm i style-loader css-loader postcss-loader autoprefixer -D

创建postcss.config.js

module.exports = {
    plugins: [require('autoprefixer')]
}

修改webpack.common.js

module: {
  rules: [
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader', 'postcss-loader'],
    },
  ]
}

完成后可在src/index.js中这样使用

import './style.css'

准备试试css-in-js(可能能到那一步)就不装less了

处理打包后的js、css

抽离css

pnpm i mini-css-extract-plugin -D

先把common中对css的处理移到dev

然后修改webpack.prod.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        MiniCssExtractPlugin.loader,
        'css-loader',
        'postcss-loader'
      ],
    },
  ],
},
plugins: [
  new MiniCssExtractPlugin({
    filename: 'css/[name].[contenthash:8].css',
  }),
],

压缩js、css

pnpm i terser-webpack-plugin optimize-css-assets-webpack-plugin -D

webpack.prod.js

const TerserJSPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')

optimization: {
  minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},

现在的dist文件夹结构如图所示

image.png

激动人心的分包

修改webpack.prod.js

optimization: {
  splitChunks: {
    // initial 入口chunk,对于异步导入的文件不处理
    // async 异步chunk,只对异步导入的文件处理
    // all 全部chunk
    chunks: 'all',
    // 缓存分组
    cacheGroups: {
      // 第三方模块
      vendor: {
        name: 'vendor',
        priority: 1, // 权限更高则优先抽离
        test: /node_modules/,
        minSize: 0, // 大小限制
        minChunks: 1, // 最少复用的次数
      },
      // 公共模块
      common: {
        name: 'common',
        priority: 0,
        minSize: 0,
        minChunks: 2,
      },
    },
  }
},

该成为React了

先从jsx开始

pnpm i react react-dom @babel/preset-react -D

.babelrc

{
    "presets": ["@babel/preset-env", "@babel/preset-react"],
    "plugins": []
}

webpack.prod.js中对js的处理移到webpack.common.jsindex.html写入<div id="root"></div>

修改index.js

import React from 'react'
import ReactDOM from 'react-dom'

ReactDOM.render(<button onClick={()=>alert('hello')}>Hello</button>, document.getElementById('root'))

完成

image.png

成为一个App

先配置一下webpack.common.js方便开发

resolve: {
  extensions: ['.js', '.json'], // 可以不写扩展名的文件类型
  // 很有用的别名,目前只写根目录
  alias: {
    '@': srcPath,
  },
},

同时给vscode也配置一下对@的路径提示,在根目录新建jsconfig.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    },
    "target": "ES6",
    "module": "commonjs",
    "allowSyntheticDefaultImports": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

.src/新建App.js

import React from 'react'

const App = () => {
  return <div>I am an App</div>
}

export default App

现在的index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from '@/App.js'

ReactDOM.render(<App />, document.getElementById('root'))

路由(react-router)

感觉比vue-router麻烦多了,先写到这