webpack4+react+antd+axios+router4+redux 學習以及腳手架搭建_014

webpack4 學習腳手架搭建

安裝和初始化

首先附上官方的文檔

github地址

https://github.com/xiaopingzh...

會不定時更新,如果覺得有幫助到你,給個Star當做鼓勵可好。

.
├── README.md
├── build
│   ├── webpack.dev.conf.js
│   ├── webpack.dll.conf.js
│   └── webpack.prod.conf.js
├── dist
├── dll
├── manifest.json
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── components
│   │   ├── Bread
│   │   │   └── Bread.js
│   │   └── SiderBar
│   │       └── SiderBar.js
│   ├── index.js
│   ├── layouts
│   │   └── BasicLayout.js
│   ├── pages
│   │   ├── Counter
│   │   │   └── Counter.js
│   │   └── Home
│   │       └── Home.js
│   ├── redux
│   │   ├── actions
│   │   │   └── counter.js
│   │   ├── reducer.js
│   │   ├── reducers
│   │   │   └── counter.js
│   │   └── store.js
│   ├── request
│   │   └── request.js
│   ├── router
│   │   └── Router.js
│   └── util
│       └── loadable.js
└── yarn.lock
  1. 新創建一個目錄並初始化npm,在本地安裝webpack,再安裝webpack-cli
>npm init

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (webpack4)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/xiaopingzhang/UCloud/webpack4/package.json:

{
  "name": "webpack4",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) yes

初始化之後按照提示一步步往下就可以了,可以輸入該項目的描述等等信息。一開始也沒有關係,後面也還可以更改。

下一步 本地安裝webpack,再安裝webpack-cli

npm install webpack webpack-cli --save-dev

==--save-dev 是你開發時候依賴的東西,--save 是你發佈之後還依賴的東西。==

>npm install webpack webpack-cli --save-dev

> [email protected] install /Users/xiaopingzhang/UCloud/webpack4/node_modules/fsevents
> node install

node-pre-gyp WARN Using needle for node-pre-gyp https download
[fsevents] Success: "/Users/xiaopingzhang/UCloud/webpack4/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node" is installed via remote

> [email protected] postinstall /Users/xiaopingzhang/UCloud/webpack4/node_modules/webpack-cli
> lightercollective


     *** Thank you for using webpack-cli! ***

Please consider donating to our open collective
     to help us maintain this package.

  https://opencollective.com/webpack/donate

                    ***

npm WARN [email protected] No description
npm WARN [email protected] No repository field.

+ [email protected]
+ [email protected]
added 458 packages from 239 contributors and audited 5208 packages in 18.624s
found 0 vulnerabilities

安裝好之後,也會顯示安裝的哪個版本,一般安裝沒有啥問題。實在安裝不成功,試一下全局安裝。

2.新建src文件夾,入口的js文件和html文件。

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

index.js文件

const component = () => {
  let element = document.createElement("div");

  element.innerHTML = "webpackworks";

  return element;
};

document.body.appendChild(component());

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Start</title>
  </head>

  <body>
    <script src="./dist/main.js"></script>
  </body>
</html>


3.學會使用webpack編譯文件

輸入 npx webpack

>npx webpack

Hash: 9ad2a368debc9967c1f4
Version: webpack 4.29.0
Time: 269ms
Built at: 2019-01-27 21:15:22
  Asset      Size  Chunks             Chunk Names
main.js  1.01 KiB       0  [emitted]  main
Entrypoint main = main.js
[0] ./src/index.js 218 bytes {0} [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

再用瀏覽器打開index.html,查看網頁是否正常的顯示了。
image

webpack 把入口文件 index.js 經過處理之後,生成 main.js

配置文件

經過第一部分的嘗試,已經初步瞭解webpack的作用,這一部分通過配置文件進行相應的一些設置。

babel

Babel 把用最新標準編寫的 JavaScript 代碼向下編譯成可以在今天隨處可用的版本。 這一過程叫做“源碼到源碼”編譯, 也被稱爲轉換編譯。
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0

新建babel配置文件.babelrc

{
   "presets": [
     "es2015",
     "react",
     "stage-0"
   ],
   "plugins": []
}
//babel-core 調用Babel的API進行轉碼
//babel-loader
//babel-preset-es2015 用於解析 ES6
//babel-preset-react 用於解析 JSX
//babel-preset-stage-0 用於解析 ES7 提案

新建配置文件

webpack.base.conf.js

webpack.dev.conf.js

webpack.prod.conf.js

分別是公共配置,開發配置,生產配置。
目前目錄結構爲

.
├── build
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   └── webpack.prod.conf.js
├── dist
│   └── main.js
├── index.html
├── package.json
└── src
    └── index.js

加載js/jsx文件

npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0

在 src 目錄下新建.babelrc

{
  "presets": [
    [
      "env",
      {
        "targets": {
          "browsers": [">1%", "last 3 versions"]
        }
      }
    ],
    "stage-2",
    "latest",
    "react"
  ],
  "plugins": [
    "syntax-dynamic-import",
    "transform-class-properties",
    <!--[-->
    <!--  "import",-->
    <!--  {-->
    <!--    "libraryName": "antd",-->
    <!--    "libraryDirectory": "es",-->
    <!--    "style": true-->
        // "style": "css" //主題設置
    <!--  }-->
    <!--]--> 
    不用antd 可以去掉
  ]
}

文件新增

    {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/, //排除
        include: [
          path.resolve(__dirname, '../src')
        ], //包括
        use: {
          loader: 'babel-loader'
        }
      },

加載CSS文件

npm install --save-dev style-loader css-loader

在配置文件裏添加

      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }

加載圖片

npm install --save-dev url-loader file-loader

在配置文件裏添加

      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: "url-loader",
            options: {
              limit: 8192
            }
          }
        ]
      }

options limit:8192意思是,小於等於8K的圖片會被轉成base64編碼,直接插入HTML中,減少HTTP請求。

加載less

在這個踩了一個坑,記得安裝 less

npm install --save-dev less-loader less

更改antd 默認主題設置需要,不用的話應該把相應的設置忽略即可。

      {
        test: /\.less$/,
        use: [
          {
            loader: 'style-loader'
          },
          {
            loader: 'css-loader' // translates CSS into CommonJS
          },
          {
            loader: 'less-loader', // compiles Less to CSS
            options: {
              modifyVars: {
                'font-size-base': '12px',
                'primary-color': '#0EA679'
              },
              javascriptEnabled: true
            }
          }
        ]
      }

加載字體

那麼,像字體這樣的其他資源如何處理呢?file-loader 和 url-loader 可以接收並加載任何文件,然後將其輸出到構建目錄。這就是說,我們可以將它們用於任何類型的文件,包括字體。更新 webpack.config.js 來處理字體文件:

      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ["file-loader"]
      }

增加HtmlWebpackPlugin

HtmlWebpackPlugin作用是生成一個HTML模板。
HtmlWebpackPlugin簡化了HTML文件的創建,以便爲你的webpack包提供服務。這對於在文件名中包含每次會隨着編譯而發生變化哈希的 webpack bundle 尤其有用。

你可以讓插件爲你生成一個HTML文件,使用lodash模板提供你自己的模板,或使用你自己的loader
首先需要安裝插件:

npm install --save-dev html-webpack-plugin

在生產配置文件裏添加

 plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      title: 'title', // 更改HTML的title的內容
      favicon: 'public/favicon.ico',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true,
      },
    }),

清理 /dist 文件夾

在每次構建前清理 /dist 文件夾.

npm install clean-webpack-plugin --save-dev
new CleanWebpackPlugin(['../dist'])

模塊熱替換

https://webpack.docschina.org...

模塊熱替換(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允許在運行時更新各種模塊,而無需進行完全刷新。
有兩種方式

  • 更改package.json
"dev": "webpack --config build/webpack.dev.config.js --color --progress --hot"
  • 更改index.js
import React from 'react';
import ReactDom from 'react-dom';

**if (module.hot) {
    module.hot.accept();
}**
//增加

  • 更改配置文件
const webpack = require('webpack');

devServer: {
    hot: true
}

plugins:[
     new webpack.HotModuleReplacementPlugin()
]

redux

https://www.redux.org.cn/
官方文檔先給上,一開始學的時候也以爲這個比較難,開始寫就不會了。
網上看看例子,自己在coding一下就差不多了。

這邊用到了一箇中間件 redux-thunk

npm install --save redux-thunk

附上寫的代碼

store

註釋的部分爲生產環境使用。

爲了方便debug代碼,在控制檯打印readux日誌。

// import { createStore, applyMiddleware } from 'redux';
// import thunk from 'redux-thunk';

// import rootReducer from './reducer';

// const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
// const store = createStoreWithMiddleware(rootReducer);

// export default store;

// 打印操作日誌,方便調試,生產環境可以去掉,用上面註釋的配置。

import thunk from "redux-thunk"; // redux 作者開發的異步處理方案 可以在action 裏傳入 dispatch getState
import { createLogger } from "redux-logger"; // 利用redux-logger打印日誌
import { createStore, applyMiddleware } from "redux"; // 引入redux createStore、中間件及compose
import { composeWithDevTools } from "redux-devtools-extension"; // devToolsEnhancer,
import reducer from "./reducer"; // 引入reducers集合

// 調用日誌打印方法 collapsed是讓action摺疊,看着舒服點
const loggerMiddleware = createLogger({ collapsed: true });

// 創建一箇中間件集合
const middleware = [thunk, loggerMiddleware];

// 創建store
const store = createStore(
  reducer,
  composeWithDevTools(applyMiddleware(...middleware))
);

export default store;

action

export const INCREMENT = 'counter/INCREMENT';
export const DECREMENT = 'counter/DECREMENT';
export const RESET = 'counter/RESET';

export function increment() {
  return { type: INCREMENT };
}

export function decrement() {
  return { type: DECREMENT };
}

export function reset() {
  return { type: RESET };
}

reducer

每個頁面的reduce文件
import { INCREMENT, DECREMENT, RESET } from '../actions/counter';

const initState = {
  count: 0,
};

export default function reducer(state = initState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        count: state.count + 1,
      };
    case DECREMENT:
      return {
        count: state.count - 1,
      };
    case RESET:
      return { count: 0 };
    default:
      return state;
  }
}
redecers 整合所有文件的reducer
import { combineReducers } from "redux";

import counter from "./reducers/counter";

export default combineReducers({
  counter
});

react-loadable

https://github.com/jamiebuild...

官方文檔先附上

// 加載頁面
import Loadable from 'react-loadable';
import React, { Component } from 'react';
import { Spin, Icon } from 'antd';

const antIcon = <Icon type="loading" style={{ fontSize: 24 }} spin />;
const antLong = (
  <Icon type="loading" style={{ fontSize: 24, color: 'red' }} spin />
);
const antError = (
  <Icon type="loading" style={{ fontSize: 24, color: 'red' }} spin />
);

export const Loading = props => {
  if (props.error) {
    return (
      <Spin
        size="large"
        tip="加載錯誤 。。。"
        indicator={antError}
        style={{ position: 'absolute', color: 'red', top: '40%', left: '50%' }}
      />
    );
  } else if (props.timedOut) {
    return (
      <Spin
        size="large"
        tip="加載超時 。。。"
        indicator={antLong}
        style={{ position: 'absolute', color: 'red', top: '40%', left: '50%' }}
      />
    );
  } else if (props.pastDelay) {
    return (
      <Spin
        size="large"
        tip="Loading 。。。"
        indicator={antError}
        style={{ position: 'absolute', color: 'red', top: '40%', left: '50%' }}
      />
    );
  } else {
    return null;
  }
};

export const importPath = ({ loader }) => {
  return Loadable({
    loader,
    loading: Loading,
    delay: 200,
    timeout: 10000
  });
};

axios 統一攔截所有的請求和返回數據

在需要用到的地方引入這個文件就ok了。只是簡單的寫了一個例子,後續再完善吧。axios使用起來很簡潔。

import axios from "axios";
import { message } from "antd";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

// 攔截所有有請求與回覆
// Add a request interceptor
axios.interceptors.request.use(
  config => {
    NProgress.start();
    return config;
  },
  error => {
    message.error("請求錯誤,請重試");
    return Promise.reject(error);
  }
);

// Add a response interceptor
axios.interceptors.response.use(
  response => {
    // NProgress.done();
    // if (response.data.RetCode === 101) {
    //   message.error(response.data.Message);
    //   return response;
    // }
    // if (response.data.RetCode === 100) {
    //   message.error(response.data.Message);
    //   return response;
    // }
    return response;
  },
  error => {
    message.error("請求錯誤,請重試");
    NProgress.done();
    return Promise.reject(error);
  }
);
export default request;

公共路徑(public path)

插件配置


 plugins: [
    // 處理html
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      path: '../public/index.html',
      inject: 'body',
      title: '管理平臺',
      favicon: 'public/favicon.ico',
      filename: 'index.html',
      hash: true,
      minify: {
        html5: true,
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      }
    }),
    new CleanWebpackPlugin(['../dist'], { allowExternal: true }),
    new BundleAnalyzerPlugin(),
    new MiniCssExtractPlugin({
      chunkFilename: '[chunkhash].css'
    }),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('../dll/manifest.json')
    }),
    new CopyWebpackPlugin([
      { from: 'dll/Dll.js', to: DIST_PATH }
    ])
  ]

html-webpack-plugin

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

new HtmlWebpackPlugin({
      template: 'public/index.html',
      path: '../public/index.html',
      inject: 'body',
      title: '管理平臺',
      favicon: 'public/favicon.ico',
      filename: 'index.html',
      hash: true,
      minify: {
        html5: true,
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      }
    }),

copy-webpack-plugin


const CopyWebpackPlugin = require('copy-webpack-plugin');

new CopyWebpackPlugin([
      { from: 'dll/Dll.js', to: DIST_PATH }
    ])

clean-webpack-plugin


const CleanWebpackPlugin = require('clean-webpack-plugin');

new CleanWebpackPlugin(['../dist'], { allowExternal: true })

webpack-bundle-analyzer

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  .BundleAnalyzerPlugin;
  
  new BundleAnalyzerPlugin(),
 

mini-css-extract-plugin

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

 new MiniCssExtractPlugin({
      chunkFilename: '[chunkhash].css'
    })
    

附上三個配置文件

webpack.dev.conf.js

const path = require('path');
const webpack = require('webpack');

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const DIST_PATH = path.resolve(__dirname, '../dist'); //生產目錄
const APP_PATH = path.resolve(__dirname, '../src'); //源文件目錄

module.exports = {
  mode: 'development',
  entry: {
    index: './src/index.js'
  },
  output: {
    path: DIST_PATH, //出口路徑
    filename: 'index.js',
    chunkFilename: 'js/[name].[chunkhash].js', //按需加載名稱
    // publicPath: "./"
  },
  // 源錯誤檢查
  devtool: 'inline-source-map',
  //模塊配置
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/, //排除
        include: [
          path.resolve(__dirname, '../src'),
          path.resolve(__dirname, '../node_modules/antd/')
        ], //包括
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      },
      //更改antd主題設置
      {
        test: /\.less$/,
        use: [
          {
            loader: 'style-loader'
          },
          {
            loader: 'css-loader' // translates CSS into CommonJS
          },
          {
            loader: 'less-loader', // compiles Less to CSS
            options: {
              modifyVars: {
                'font-size-base': '12px',
                'primary-color': '#0EA679'
              },
              javascriptEnabled: true
            }
          }
        ]
      }
    ]
  },
  //插件
  plugins: [
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      path: '../public/index.html',
      inject: 'body',
      favicon: 'public/favicon.ico',
      title: '管理平臺',
      overlay: true,
      minify: {
        html5: false
      },
      hash: true
    }),
    // 熱更新
    new webpack.HotModuleReplacementPlugin(),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('../dll/manifest.json')
    }),
    new CopyWebpackPlugin([
      { from: 'dll/Dll.js', to: DIST_PATH }
    ])
  ],
  // 熱更新
  devServer: {
    port: '3300',
    contentBase: DIST_PATH,
    historyApiFallback: true,
    hot: true, // 開啓
    https: false,
    compress: false,
    noInfo: true,
    open: true,
    proxy: {
      // '/': {
      //   target: '',
      //   changeOrigin: true,
      //   secure: false,
      // },
    }
  }
};

webpack.dll.conf.js

const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');

const vendors = [
  'antd',
  'axios',
  'nprogress',
  'react',
  'react-dom',
  'react-loadable',
  'react-redux',
  'react-router',
  'react-router-dom',
  'redux'
];

module.exports = {
  entry: {
    vendor: vendors
  },
  output: {
    path: path.resolve(__dirname, '../dll'),
    filename: 'Dll.js',
    library: '[name]_[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.resolve(__dirname, '../dll', 'manifest.json'),
      name: '[name]_[hash]',
      context: __dirname
    }),
    new CleanWebpackPlugin(['../dll'], { allowExternal: true })
  ]
};

webpack.prod.conf.js


const path = require('path');
const webpack = require('webpack');

const CopyWebpackPlugin = require('copy-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
  .BundleAnalyzerPlugin;

const DIST_PATH = path.resolve(__dirname, '../dist'); //生產目錄

module.exports = {
  mode: 'production',
  entry: {
    index: './src/index.js'
  },
  output: {
    path: DIST_PATH, //出口路徑
    filename: 'index.js',
    chunkFilename: '[name]_[hash].js', //按需加載名稱
    // publicPath: './'
  },
  // 源錯誤檢查
  devtool: 'source-map',
  //模塊配置
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /(node_modules|bower_components)/, //排除
        include: [
          path.resolve(__dirname, '../src'),
          path.resolve(__dirname, '../node_modules/antd/')
        ], //包括
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192
            }
          }
        ]
      },
      //更改antd主題設置
      {
        test: /\.less$/,
        use: [
          {
            loader: 'style-loader'
          },
          {
            loader: 'css-loader' // translates CSS into CommonJS
          },
          {
            loader: 'less-loader', // compiles Less to CSS
            options: {
              minimize: true,
              modifyVars: {
                'font-size-base': '12px',
                'primary-color': '#0EA679'
              },
              javascriptEnabled: true
            }
          }
        ]
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }
    ]
  },
  //插件
  plugins: [
    // 處理html
    new HtmlWebpackPlugin({
      template: 'public/index.html',
      path: '../public/index.html',
      inject: 'body',
      title: '管理平臺',
      favicon: 'public/favicon.ico',
      filename: 'index.html',
      hash: true,
      minify: {
        html5: true,
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
      }
    }),
    new CleanWebpackPlugin(['../dist'], { allowExternal: true }),
    new BundleAnalyzerPlugin(),
    new MiniCssExtractPlugin({
      chunkFilename: '[chunkhash].css'
    }),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('../dll/manifest.json')
    }),
    new CopyWebpackPlugin([
      { from: 'dll/Dll.js', to: DIST_PATH }
    ])
  ]
  // 熱更新
};

學習過程中的踩坑

生產環境打包報錯

ERROR in Path must be a string. Received undefined
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    

這個錯誤不影響打包結果,應該是版本問題導致。

https://github.com/jantimon/h...

寫完才發現有些忘記記錄了,會保持更新。

學習的過程中也學習參考了其他優秀的博客和github,以及文檔。

https://github.com/brickspert...

https://github.com/NewPrototy...

https://github.com/axios/axios

https://github.com/jamiebuild...

https://www.webpackjs.com/con...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章