使用webpack4从零构建React + React-router + Redux + Scss/Css开发环境

  前言  

    最近这几年,前端变化非常大,各种新框架应接不暇,从最开始几个html,css,js文件就搞定了前端,到现在前端的模块化,工程化,旨在构建出易于维护的前端系统。目前为止,前端三大框架Vue/React/Angular(Vue官网:https://cn.vuejs.org/index.html,

React官网:https://reactjs.org/,Angular官网:https://angularjs.org/),身为一个前端开发人员,不了解这些框架,感觉都out了。个人比较喜欢React,主要是jsx语法很灵活,以及其强大的生态系统,但React的资料国内非常的少,关于使用webpack4构建React开发环境就更少了,本文章将带来读者使用webpack4(目前是最新的,webpack官网:https://webpack.js.org/)从零构建一个React + React-router + Redux + Scss/Css的开发环境,如果是刚入门weboack4的读者,也不要紧,本文尽量使用最浅显易懂的方式告诉你webpack是干什么的,以及如何写webpack配置文件,对于React-router,Redux其实也不是什么高大上的东西,文章后面都会详细介绍。

     在构建过程中,会介绍基本的loader如处理jsx/js的babel-loader(babel的具体内容请查看官网:https://babeljs.io/),处理css/scss(Scss的内容请查看官网:https://sass-lang.com/)的css-loader和scss-loader,以及处理静态资源文件(png/jpg/gif)的url-loader,以及做css兼容处理的postcss-loader(postcss的内容请查看官网:https://postcss.org/),然后会介绍一些插件,比如并行压缩代码插件,css压缩插件,以及肯会使用happypack来加速打包。

     文章可能比较长,请一定要耐心看,一步一步跟着做,我会把每一步的webpack配置文件都发出来,保证每一个webpack配置文件都是能用的,当自己亲手搭建完一个React开发环境,自己也会有成就感,本项目已经放github欢迎下载:https://github.com/sundial-dreams/ReactRedux/tree/master(不介意的话,给个star也行)。

  

文章目录


yarn安装

webpack4介绍

项目目录结构

项目搭建

使用babel-loader处理js/jsx文件

使用css-loader以及sass-loader处理css/scss文件

使用webpackDevServer

使用postcss

使用url-loader处理资源文件(图片等)

使用webpack插件

优化部分

使用happlypack优化打包速度(webpack4已经不需要它了)

别名配置

React,React-router,Redux的介绍和使用

 

yarn安装   

     使用webpack4构建的过程中,可能涉及到很多包的安装,强烈建议使用yarn来安装包(用npm安装包也是一样的),yarn是 Facebook 提供的替代 npm 的工具,可以加速 node 模块的下载,跟npm相比,快的不是一点点(并行下载),而且会缓存下载过的包,所以下载过的包,都无需重新下载。

     首先设置npm镜像

npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global

     使用npm安装yarn(当然设置了npm镜像的话,安装速度也不算太慢)

npm install -g yarn

     安装完 yarn 后同理也要设置镜像源

yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global

yarn的使用也很简单

     初始化一个新项目

yarn init

#等价于npm init 不过他生成的是package.json和yarn.lock的文件

      添加依赖包

yarn add [package]
yarn add [package] --dev 添加到devDependencies 等价于npm install [package] --save-dev
yarn add [package] --peer 添加到peerDependencies
yarn add [package] --optional 添加到optionalDependencies

     升级依赖包

yarn upgrade [package] 

#upgrade这个单词是升级的意思

     移除依赖包

yarn remove [package]

     安装项目的全部依赖

yarn 或者 yarn install

webpack介绍

     这里我简单介绍一下webpack,3个月前,我也不知道webpack是什么鬼,不过随着自己对前端认识越来越深,现在看来webpack真是个无所不能的打包工具,不仅使前端有了模块化,工程化的概念,而且在前后端分离方面也有很大的贡献。简单来说webpack可以让前端代码可以向后端那样模块化,通过import 或 require方式可以导入模块,export 或 exports可以导出模块(分别对应es6和nodejs的模块系统),像后端一样,所有的导入肯定有一个入口点,这就类似于后端的主模块一样(想想主模块一定会把所有依赖的模块给导进来),webpack就是从这个入口点出发开始打包,一直找通过import等方式导入的模块,打包到一起,最后输出html+css+js+png等静态文件。

这是官网给的图,其实也很好理解,从入口(左上角)一直找依赖(import 进来的东西),最后把它们一起打包输出成js+css+jpg+png,因为什么文件都可以通过import来导入,所以就必须得对每一种文件进行处理,import 了 css文件就必须得对css文件进行处理,故就要用css-loader 其他文件也是一样的,说到这里,想信对webpack也能有一定的理解了吧 ,如果没有理解,可以试着跟着下面慢慢做,写着写着就理解了。

目录结构 

接下来我们可以开始我们的构建了项目了,首先来看我们最终的目录结构长什么样

目录结构如下

 

ReactTest

               |___ dist  打包目录

               |___ assets 静态,公共资源目录

               |___  public 静态,公共资源目录

                            |___ index.html 入口

               |___ src 

                            |___ components 组件目录

                                               |___ Button 组件Button

                                                                  |___ button.jsx 组件文件

                                                                  |___ button.scss 样式文件 或者是button.css

                            |___ pages 页面目录

                                                |___ Index 首页

                                                                  |___ index.jsx 首页

                                                                  |___ index.scss 样式文件

                            |___ reduxs 存储redux状态,action,reducer目录

                                                |___ actions 存放redux action目录

                                                                   |___ action.js

                                                |___ reducer 存放redux reducer目录

                                                                   |___ reduce.js

                            |___ router 路由目录

                                                 |___ router.js

                            |___ index.jsx 入口

               |___ .babelrc babel配置文件

               |___ postcss.config.js postcss配置文件

               |___ webpack.common.config.js webpack公共配置文件

               |___ webpack.config.js webpack配置文件 打包时使用

               |___ webpack.dev.config.js webpack配置文件 开发时使用

                  

 

项目搭建

    先建个项目目录ReactTest(名随便起),然后在当前项目下打开终端输入命令yarn init初始化项目,然后一路回车,最后应该会在项目目录下出现package.json文件,新建src文件夹,dist文件夹,public文件夹

然后安装webpack

yarn add webpack --dev

在项目目录下新建webpack.config.js,src下新建index.jsx,public下新建index.html其中index.jsx和index.html为入口

//webpack.config.js
const {resolve,join} = require("path");
const paths = {
  src: resolve(__dirname, "src"),
  dist: resolve(__dirname, "dist"),
  public: resolve(__dirname,"public")
};
module.exports = {
  entry: [
      join(paths.public,"index.html"),
      join(paths.src,"index.jsx")
  ],
  output: {
    path: paths.dist,
    filename: "[name].js"
  }
};

现在webpack还不能跑,jsx和html文件没有处理,首先处理jsx文件,我们要使用babel-loader(关于webpack配置文件里得写些啥,可以参考这里https://webpack.docschina.org/configuration/#%E9%80%89%E9%A1%B9)

 

使用babel-loader处理js/jsx文件

首先安装babel的东西,

@babel/core  # babel 核心的东西 
@babel/preset-react # 转换 JSX 语法并去除类型注释
@babel/preset-env # 转换es6语法  如for of 箭头函数等等
@babel/plugin-transform-runtime  # 复用插件 具体可以看这里https://babel.docschina.org/docs/en/babel-plugin-transform-runtime#docsNav
@babel/runtime 
@babel/polyfill # 转换es6的新函数或对象如Promise,Set,Map,Array.from等

使用yarn安装以上包

yarn add @babel/core @babel/preset-react @babel/preset-env @babel/plugin-transform-runtime @babel/polyfill @babel/runtime  --dev 

安装babel-loader

yarn add babel-loader --dev

现在还要处理入口的html文件,需要html-loader,以及html-webpack-plugin插件

安装html-loader和html-webpack-plugin

yarn add html-webpack-plugin html-loader --dev

然后修改webpack.config.js

//webpack.config.js
const {resolve, join} = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const paths = {
  src: resolve(__dirname, "src"),
  dist: resolve(__dirname, "dist"),
  public: resolve(__dirname, "public")
};
module.exports = {
  mode: "development",
  entry: [
    join(paths.public, "index.html"),
    "@babel/polyfill",
    join(paths.src, "index.jsx"),

  ],
  output: {
    path: paths.dist,
    filename: "[name].js"
  },
  module: {
    rules: [
      {
        //处理jsx,js
        test: /\.(jsx?)$/,
        exclude: /node_modules/,
        include: resolve(__dirname, "src"),
        use: {
          loader: "babel-loader",
          options: {
            cacheDirectory: true
          }
        }
      },
      {
        ///处理html
        test: /\.html?/,
        exclude: /node_modules/,
        include: resolve(__dirname, "public"),
        use: {
          loader: "html-loader",
          options: {
            minimize: true  //压缩html代码
          }
        }
      }
     ]
    }
   ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: join(paths.public, "index.html"),
      filename: "index.html",
      title: "Caching"
    })
  ]
};

在项目目录下新建.babelrc,代码如下

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

好了,现在我们的webpack能处理jsx和html文件了,接下来就是安装react,react-dom了

yarn add react react-dom --dev

然后在index.jsx中写点东西

//index.jsx

import React, {Component} from "react";
import ReactDOM from "react-dom";

class App extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="app">
        <h1>hello , I am ok</h1>
      </div>
    )
  }
}

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

此时我们还需要webpack-cli才能打包项目,使用yarn安装webpack-cli

yarn add webpack-cli --dev

然后添加个运行脚本,修改package.json文件

{
...省略
"scripts": {
    "build": "./node_modules/.bin/webpack -p"    
  }
 ...
}

加了这个后,打包只需要yarn run build就ok了,轻松简单

然后看看打包结果

没有报错,一切ok,然后可以看到dist目录下打包出来了index.html和main.js文件,浏览器打开index.html文件

关于jsx这块算是差不多完事了,关于优化的部分后面将会涉及到

使用css-loader以及sass-loader处理css/scss文件

处理完js,jsx接下来就是处理scss或css文件了,处理scss前我们得先安装node-sass包,这是一个专门处理scss或sass的包,更多详情可以看这里https://github.com/sass/node-sass

使用yarn安装

yarn add node-sass --dev

然后安装css-loader和sass-loader处理css/sass/scss文件

yarn add css-loader sass-loader --dev

然后再安装最后一个mini-css-extract-plugin,能将css单独提取到一个文件里,支持动态导入等,我觉得直接点这里能更清楚这是干嘛的https://github.com/webpack-contrib/mini-css-extract-plugin

yarn add mini-css-extract-plugin --dev

一切准备就绪,然后开始继续写我们的webpack文件

//webpack.config.js
const {resolve, join} = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const paths = {
  src: resolve(__dirname, "src"),
  dist: resolve(__dirname, "dist"),
  public: resolve(__dirname, "public")
};
module.exports = {
  mode: "development",
  entry: [
    join(paths.public, "index.html"),
    "@babel/polyfill",
    join(paths.src, "index.jsx"),

  ],
  output: {
    path: paths.dist,
    filename: "[name].js"
  },
  module: {
    rules: [
      {
        //处理jsx,js
        test: /\.(jsx?)$/,
        exclude: /node_modules/,
        include: resolve(__dirname, "src"),
        use: {
          loader: "babel-loader",
          options: {
            cacheDirectory: true
          }
        }
      },
      {
        ///处理html
        test: /\.html?/,
        exclude: /node_modules/,
        include: resolve(__dirname, "public"),
        use: {
          loader: "html-loader",
          options: {
            minimize: true  //压缩html代码
          }
        }
      },
      {
        //处理css/scss/sass
        test:/\.(css|scss|sass)$/,
        exclude: /node_modules/,
        include: resolve(__dirname, "src"),
        loaders:[
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              sourceMap:true,  //资源映射
              modules:true,    //是否允许模块
              importLoaders:20
            }
          },
          {
            loader: "sass-loader",
            options:{
              sourceMap:true
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: join(paths.public, "index.html"),
      filename: "index.html",
      title: "Caching"
    }),
    new MiniCssExtractPlugin({
      filename:"[name].css",
      chunkFilename:"[id].css"
    })
  ]
};

   需要提一下sourceMap这个东西,很多地方都能写sourceMap那么这到底是啥东西呢,其实再部署前端的时候,我们会对代码压缩打包来减少代码大小,提高访问速度,那么问题就来了,压缩的代码报错了咋办,这时候浏览器出来的报错信息基本上都不是人看的,这时就需要source map 来还原真实的出错位置。

接下来,我们在src下新建一个test.scss看看好不好使

//test.scss
$width: 400px;
$height: 400px;
@mixin Size($width,$height) {
  width: $width;
  height: $height;
}

@mixin Center() {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.app {
  @include Size($width, $height);
  border-radius: 10px;
  background: blueviolet;
  box-shadow: 0 0 10px;
  @include Center();
  h1 {
    color: white;
    @include Center();
  }
}

在index.jsx中导入,以及使用

import React, {Component} from "react";
import ReactDOM from "react-dom";
import style from "./test.scss"; //导入
class App extends Component {
  constructor(props) {
    super(props);
  }

  render() {
          
    return (                      //使用
      <div className={style.app}>   
        <h1>hello , I am ok</h1> 
      </div>
    )
  }
}

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

运行webpack,使用命令yarn run build

ok没报错,这时可以看到,dist目录下多了main.css文件,在浏览器打开index.html

处理scss这部分就告一段落了,读者有兴趣的话,也可以写css文件来试试

 

使用webpackDevServer

    仔细想想,每改一处代码,就要yarn run build 重新打包一遍,这显然是不可取的,所以我们使用devServer来监听所有文件的修改,当有文件修改时,则自动执行打包,在项目目录下新建webpack.dev.config.js,这是开发时使用的webpack配置文件,理论上跟webpack.config.js差不多,只要把webpack.config.js复制粘贴过来就ok了,然后再改改不一样的地方,比如多了devServer这一项,而且少部分地方也有不一样的, 那么如果这么做的话,如果我后面修改了webpack.dev.config.js,比如加个处理图片的rule,这时webpack.config.js也要跟着改,似乎有点不划算,原本只要改一配置文件,现在要改两个,所以我们把webpack.config.js和webpack.dev.config.js的公共部分给抽出来放到webpack.common.config.js就好了,现在项目里新建一个webpack.common.config.js,将之前在webpack.config.js写的内容拷贝过去,然后区分开发环境和生产环境,故修改webpack.common.config.js

先安装webpack-dev-server,

yarn add webpack-dev-server --dev

 然后修改webpack.common.config.js

//webpack.common.config.js
const {resolve, join} = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const paths = {
  src: resolve(__dirname, "src"),
  dist: resolve(__dirname, "dist"),
  public: resolve(__dirname, "public")
};

const evn = process.argv.pop();//获取当前的环境,生产或开发
const EVN = {
  pro: "production",
  dev: "development"
};

module.exports = {
  entry: [
    join(paths.public, "index.html"),
    "@babel/polyfill",
    join(paths.src, "index.jsx"),
  ],
  output: {
    path: paths.dist,
    chunkFilename: evn === EVN.dev ? "[name].[hash].js" : "[name].js",
    filename: evn === EVN.dev ? "[name].[hash].js" : "[name].js",
    publicPath: resolve(__dirname, "assets")
  },
  module: {
    rules: [
      {
        //处理jsx,js
        test: /\.(jsx?)$/,
        exclude: /node_modules/,
        include: resolve(__dirname, "src"),
        use: {
          loader: "babel-loader",
          options: {
            cacheDirectory: evn === EVN.dev,
            sourceMap: evn === EVN.dev
          }
        }
      },
      {
        ///处理html
        test: /\.html?/,
        exclude: /node_modules/,
        include: resolve(__dirname, "public"),
        use: {
          loader: "html-loader",
          options: {
            minimize: evn === EVN.dev,  //压缩html代码
            sourceMap: evn === EVN.dev  //生产环境可以不用资源映射
          }
        }
      },
      {
        //处理css/scss/sass
        test: /\.(css|scss|sass)$/,
        exclude: /node_modules/,
        include: resolve(__dirname, "src"),
        loaders: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              sourceMap: evn === EVN.dev,  //资源映射
              modules: true,    //是否允许模块
              importLoaders: 20,
              localIdentName: evn === EVN.dev ? "[path][name]__[local]--[hash:base64:5]" : ""
            }
          },
          {
            loader: "sass-loader",
            options: {
              sourceMap: evn === EVN.dev //生产环境没必要使用sourceMap了
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: join(paths.public, "index.html"),
      filename: "index.html",
      title: "react"
    }),
    new MiniCssExtractPlugin({
      filename: evn === EVN.dev ? "[name].[hash].css" : "[name].css",
      chunkFilename: evn === EVN.dev ? "[id].[hash].css" : "[id].css"
    }),
  ]
};

因为涉及到webpack的合并,所以我们使用webpack官方的webpack.merge(传送门:https://www.npmjs.com/package/webpack-merge)来合并

安装

yarn add webpack-merge --dev

修改webpack.dev.config.js如下(关于devServer更详细的内容:https://webpack.js.org/configuration/dev-server)

//webpack.dev.config.js
const common = require("./webpack.common.config");
const {resolve} = require("path");
const merge = require("webpack-merge");
const webpack = require("webpack");

module.exports = merge(common, {   //合并两个webpack文件
  devServer: {
    port: 9000,
    contentBase: [resolve(__dirname, "public"),resolve(__dirname,"dist")], //找public下的index.html
    compress: true,
    hot: true,                     //模块热加载
    inline: true,
    open: 'Chrome',                //构建完成时自动打开浏览器
    openPage: ""
  },
  devtool: "inline-source-map",      //方便调试,将src目录下的资源映射到浏览器中
  mode: "development",
  plugins: [
    new webpack.HotModuleReplacementPlugin(), //使用模块热加载插件
  ]
});

修改webpack.config.js如下

//webpack.config.js
const common = require("./webpack.common.config");
const merge = require("webpack-merge");
module.exports = merge(common, {
  devtool: "none",
  mode:"production"
});

 在package.json中添加start脚本

{
 ....
  "scripts": {
    "build": "./node_modules/.bin/webpack --mode production",
    "start": "./node_modules/.bin/webpack-dev-server --config ./webpack.dev.config.js --process  --color --mode development"
  }
 ....
}

一切都准备ok,我们输入yarn run start 运行起来

跑起来没有任何问题,而且还自动打开google浏览器(自己修改index.jsx看看能不能自动编译)

顺便一提,devServer打包的东西是放到内存里的(所以就比较快一点),不是放到某个文件夹下(如dist),所以dist文件夹下不会出现任何打包的文件,如果你开发完了,那么你就可以使用yarn run build 将所有文件打包到dist文件夹下,然后再进行部署。

使用postcss

   简单来说postcss就是做css转换和兼容处理的,比如自动添加css前缀,还能转换最新的css属性,如果还不明白,请看官网https://postcss.org/

废话不多说,先安装postcss以及对应的webpack loader即postcss-loader

yarn add postcss postcss-loader --dev

然后还需要安装几个postcss的插件

postcss-preset-env # 使用明天的css,可以使用css变量等新属性
autoprefixer # 自动加前缀
postcss-scss # scss语法
postcss-flexbugs-fixes # 解决flex问题的插件
precss # 让编译后的css更好看
postcss-nested #处理jsx里面的css
astroturf   #处理jsx里面的css

postcss-syntax #自动根据扩展名切换语法
postcss-safe-parser # 查找css中的错误,并修补
postcss-sass # sass语法
postcss-scss # scss语法

(更多postcss的插件可以查看这里https://www.npmjs.com/package/postcss)

使用yarn安装以上插件

yarn add postcss-preset-env autoprefixer postcss-scss postcss-flexbugs-fixes precss postcss-nested astroturf --dev
yarn add postcss-syntax postcss-safe-parser postcss-sass postcss-scss --dev

修改webpack.common.config.js

//webpack.common.config.js
const {resolve, join} = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const paths = {
  src: resolve(__dirname, "src"),
  dist: resolve(__dirname, "dist"),
  public: resolve(__dirname, "public")
};

const env = process.argv.pop();//获取当前的环境,生产或开发

const ENV = {
  pro: "production",
  dev: "development"
};

const isDev = env === ENV.dev;

module.exports = {
  entry: [
    join(paths.public, "index.html"),
    "@babel/polyfill",
    join(paths.src, "index.jsx"),
  ],
  output: {
    path: paths.dist,
    chunkFilename: isDev ? "[name].[hash].js" : "[name].js",
    filename: isDev ? "[name].[hash].js" : "[name].js",
  },
  module: {
    rules: [
      {
        //处理jsx,js
        test: /\.(jsx?)$/,
        exclude: /node_modules/,
        include: resolve(__dirname, "src"),
        loaders: [
          {
            loader: "babel-loader",
            options: {
              cacheDirectory: isDev,
              sourceMap: isDev
            }
          },
          {//处理jsx里面的css
            loader: 'astroturf/loader',
            options: {extension: '.scss'},
          }
        ]
      },
      {
        ///处理html
        test: /\.html?/,
        exclude: /node_modules/,
        include: resolve(__dirname, "public"),
        use: {
          loader: "html-loader",
          options: {
            minimize: isDev,  //压缩html代码
            sourceMap: isDev  //生产环境可以不用资源映射
          }
        }
      },
      {
        //处理css/scss/sass
        test: /\.(css|scss|sass)$/,
        exclude: /node_modules/,
        include: resolve(__dirname, "src"),
        loaders: [
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              sourceMap: isDev,  //资源映射
              modules: true,    //是否允许模块
              importLoaders: 20,
              localIdentName: isDev ? "[path][name]__[local]__[hash:base64:5]" : ""
            }
          },
          {
            loader: "sass-loader",
            options: {
              sourceMap: isDev //生产环境没必要使用sourceMap了
            }
          },
          {//使用postcss
            loader: "postcss-loader",
            options: {
              sourceMap: isDev
            }
          },
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: join(paths.public, "index.html"),
      filename: "index.html",
      title: "react"
    }),
    new MiniCssExtractPlugin({
      filename: isDev ? "[name].[hash].css" : "[name].css",
      chunkFilename: isDev ? "[id].[hash].css" : "[id].css"
    })
  ]
};

在项目目录下新建个postcss.config.js文件

//postcss.config.js
const flexbugsFixes = require("postcss-flexbugs-fixes");
const presetEnv = require("postcss-preset-env");
const autoprefixer = require("autoprefixer");
const precss = require("precss");
const nested = require("postcss-nested");
//根据文件扩展名来选择语法
const syntax = require("postcss-syntax")({
  sass: require("postcss-sass"),
  css: "postcss-safe-parser",
  scss: "postcss-scss"
});
module.exports = {
  syntax: syntax,
  //插件
  plugins: [
    flexbugsFixes,
    presetEnv({
      autoprefixer: {
        flexbox: 'no-2009',
      },
      stage: 3,
    }),
    autoprefixer({grid: true}),
    precss,
    nested,
  ]
};

使用yarn run start 运行一下

可以看到,css文件里确实有加前缀,而且flex也有做兼容,不信读者可以自己试试。

关于postcss的部分就到此为止了,上面使用了很多插件,有兴趣的读者可以自己去查一查。

 

使用url-loader处理资源文件(图片等)

现在我们已经处理了css/scss/sass,js/jsx,html文件了,还差资源文件没有处理,如图像等

处理资源文件,我们使用url-loader

使用yarn 安装url-loader

yarn add url-loader --dev

然后准备回滚的loader(url-loader处理不了的文件)

 我使用responsive-loader(file-loader也行,默认就是file-loader,如果要使用file-loader只要安装上file-loader就可以了,当url-loader处理不了的时候会自动调f用ile-loader),

安装responsive-loader,另外还得安装jimp(responsive-loader依赖了这个)

yarn add responsive-loader jimp --dev

然后继续修改webpack.common.config.js

 //前面的都一样,只要在module下的rules下加上这一条规则就好了
  {//处理图片文件
        test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
        exclude: /node_modules/,
        include: resolve(__dirname, "src"),
        loader: require.resolve("url-loader"),
        options:{
          limit: 8192,//图片在这个范围内,会将图片变成base64减少http请求
          fallback: "responsive-loader" //回退的loader
        }
   }

ok我们来试试好不好使,在index.jsx目录那里放一张图片,然后修改index.jsx

//index.jsx
import React, {Component} from "react";
import ReactDOM from "react-dom";
import style from "./test.scss";
import image from "./a.jpg";//导入图片
class App extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className={style.app}>
        <img src={image}/>
      </div>
    )
  }
}

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

顺便在改改样式

$width: 400px;
$height: 400px;
@mixin Size($width,$height) {
  width: $width;
  height: $height;
}

@mixin Center() {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.app {
  @include Size($width, $height);
  box-shadow: 0 0 10px;
  @include Center();
  flex: 1;
  overflow: hidden;
  img {
    width: 100%;
    height: 100%;
  }
  h1 {
    color: white;
    @include Center();
  }
  span {
    color: white;
  }
}

运行一下,yarn run start

可以看到,图片能被正常加载,图片这一块就算完事了。

 使用webpack插件

webpack插件是非常有用的东西(更多插件信息请查看这里:https://webpack.docschina.org/plugins/commons-chunk-plugin),我使用2个插件来进行打包优化

uglifyjs-webpack-plugin #压缩js代码插件

SourceMapDevToolPlugin # 对 source map 进行更细粒度的控制

这里只需要安装uglifyjs-webpack-plugin即可,其余的webpack自带

使用yarn安装

yarn add uglifyjs-webpack-plugin --dev

然后修改webpack.common.config.js

//webpack.common.config.js
const {resolve, join} = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpack = require("webpack");
const UglifyjsPlugin = require("uglifyjs-webpack-plugin");
const os = require("os");

const paths = {
  src: resolve(__dirname, "src"),
  dist: resolve(__dirname, "dist"),
  public: resolve(__dirname, "public")
};

const env = process.argv.pop();//获取当前的环境,生产或开发

const ENV = {
  pro: "production",
  dev: "development"
};

const isDev = env === ENV.dev;

module.exports = {
  entry: [
    join(paths.public, "index.html"),
    require.resolve("@babel/polyfill"),
    join(paths.src, "index.jsx"),
  ],
  output: {
    path: paths.dist,
    chunkFilename: isDev ? "[name].[hash].chunk.js" : "[name].chunk.js",
    filename: isDev ? "[name].[hash].js" : "[name].js",
  },
  module: {
    rules: [
      {
        //处理jsx,js
        test: /\.(jsx?)$/,
        exclude: /node_modules/,
        include: paths.src,
        loaders: [
          {
            loader: require.resolve("babel-loader"),
            options: {
              cacheDirectory: isDev,
              sourceMap: isDev
            }
          },
          {//处理jsx里面的css
            loader: require.resolve("astroturf/loader"),
            options: {extension: ".scss"},
          }
        ]
      },
      {
        ///处理html
        test: /\.html?/,
        exclude: /node_modules/,
        include: paths.public,
        use: {
          loader: require.resolve("html-loader"),
          options: {
            minimize: isDev,  //压缩html代码
            sourceMap: isDev  //生产环境可以不用资源映射
          }
        }
      },
      {
        //处理css/scss/sass
        test: /\.(css|scss|sass)$/,
        exclude: /node_modules/,
        include: paths.src,
        loaders: [
          MiniCssExtractPlugin.loader,
          {
            loader: require.resolve("css-loader"),
            options: {
              sourceMap: isDev,  //资源映射
              modules: true,    //是否允许模块
              importLoaders: 20,
              localIdentName: isDev ? "[path][name]__[local]__[hash:base64:5]" : ""
            }
          },
          {
            loader: require.resolve("sass-loader"),
            options: {
              sourceMap: isDev //生产环境没必要使用sourceMap了
            }
          },
          {//使用postcss
            loader: require.resolve("postcss-loader"),
            options: {
              sourceMap: isDev
            }
          },
        ]
      },
      {//处理图片文件
        test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
        exclude: /node_modules/,
        include: paths.src,
        loader: require.resolve("url-loader"),
        options: {
          limit: 8192,//图片在这个范围内,会将图片变成base64减少http请求
          fallback: require.resolve("responsive-loader") //回退的loader
        }
      }
    ]
  },
  //插件项
  plugins: [
    new HtmlWebpackPlugin({
      template: join(paths.public, "index.html"),
      filename: "index.html",
      inject: true,
      minify: isDev ? false : {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    }),
    new MiniCssExtractPlugin({
      filename: isDev ? "[name].[hash].css" : "[name].css",
      chunkFilename: isDev ? "[id].[hash].chunk.css" : "[id].chunk.css"
    }),
    //资源映射
    new webpack.SourceMapDevToolPlugin({
      filename: "[name].js.map",
      exclude: ['vendor.js']
    }),
    //js压缩插件
    new UglifyjsPlugin({
      test: /\.jsx?/,
      include: paths.src,
      exclude: /node_modules/,
      cache: true,
      parallel: os.cpus().length,
      sourceMap: true,
      uglifyOptions: {
        ie8: true,
      }
    }),
  ]
};

插件的话目前这几个就够了,以后要是缺什么可以在安什么。

 

优化部分

webpack4里面已经把优化的内容集成在了一起,即 optimization选项,更多内容可以参考官网https://webpack.docschina.org/configuration/optimization/

我们需要安装两个插件

terser-webpack-plugin  # 使用这个插件来优化压缩js
optimize-css-assets-webpack-plugin # 优化css

使用yarn安装上述插件

yarn add terser-webpack-plugin optimize-css-assets-webpack-plugin --dev

我们继续修改webpack.common.config.js

//webpack.common.config.js
const {resolve, join} = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const webpack = require("webpack");
const UglifyjsPlugin = require("uglifyjs-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const SafeParser = require("postcss-safe-parser");
const os = require("os");

const paths = {
  src: resolve(__dirname, "src"),
  dist: resolve(__dirname, "dist"),
  public: resolve(__dirname, "public")
};

const env = process.argv.pop();//获取当前的环境,生产或开发

const ENV = {
  pro: "production",
  dev: "development"
};

const isDev = env === ENV.dev;

module.exports = {
  entry: [
    join(paths.public, "index.html"),
    require.resolve("@babel/polyfill"),
    join(paths.src, "index.jsx"),
  ],
  output: {
    path: paths.dist,
    chunkFilename: isDev ? "[name].[hash].chunk.js" : "[name].chunk.js",
    filename: isDev ? "[name].[hash].js" : "[name].js",
  },
  module: {
    rules: [
      {
        //处理jsx,js
        test: /\.(jsx?)$/,
        exclude: /node_modules/,
        include: paths.src,
        loaders: [
          {
            loader: require.resolve("babel-loader"),
            options: {
              cacheDirectory: false,
              sourceMap: isDev
            }
          },
          {//处理jsx里面的css
            loader: require.resolve("astroturf/loader"),
            options: {extension: ".scss"},
          }
        ]
      },
      {
        ///处理html
        test: /\.html?/,
        exclude: /node_modules/,
        include: paths.public,
        use: {
          loader: require.resolve("html-loader"),
          options: {
            minimize: isDev,  //压缩html代码
            sourceMap: isDev  //生产环境可以不用资源映射
          }
        }
      },
      {
        //处理css/scss/sass
        test: /\.(css|scss|sass)$/,
        exclude: /node_modules/,
        include: paths.src,
        loaders: [
          MiniCssExtractPlugin.loader,
          {
            loader: require.resolve("css-loader"),
            options: {
              sourceMap: isDev,  //资源映射
              modules: true,    //是否允许模块
              importLoaders: 20,
              localIdentName: isDev ? "[path][name]__[local]__[hash:base64:5]" : ""
            }
          },
          {
            loader: require.resolve("sass-loader"),
            options: {
              sourceMap: isDev //生产环境没必要使用sourceMap了
            }
          },
          {//使用postcss
            loader: require.resolve("postcss-loader"),
            options: {
              sourceMap: isDev
            }
          },
        ]
      },
      {//处理图片文件
        test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
        exclude: /node_modules/,
        include: paths.src,
        loader: require.resolve("url-loader"),
        options: {
          limit: 8192,//图片在这个范围内,会将图片变成base64减少http请求
          fallback: require.resolve("responsive-loader") //回退的loader
        }
      }
    ]
  },
  //优化

  optimization: {
    minimize: !isDev,//是否压缩
    minimizer: [
     // 优化css
      new OptimizeCssAssetsPlugin({
        cssProcessorOptions: {
          parser: SafeParser,
          map: {inline: false, annotation: true},
        }
      }),

      new TerserPlugin({
        test:/\.jsx?/,
        include:paths.src,
        exclude:/node_modules/,
        terserOptions: {
          parse: {
            ecma: 8
          },
          compress: {
            ecma: 5,
            warning: false,
            comparisons: false,
            inline: 2
          },
          mangle: {
            safari10: true
          },
          output: {
            ecma: 5,
            comment: false,
            ascii_only: true
          },
          parallel: os.cpus().length,
          cache: true,
          sourceMap: isDev
        }
      })
    ],
    namedModules: true,
    namedChunks: true,
    removeAvailableModules: true,
    removeEmptyChunks: true,
    mergeDuplicateChunks: true,
    moduleIds: "hashed",
    splitChunks: {
      chunks: "async",
      name: true,
      cacheGroups: {
       vendor: {
         test:/[\\/]node_modules[\\/]/,
         name: "vendors",
         chunks:"all"
       }
      }
    },
    runtimeChunk: true
  },
  //插件项
  plugins: [
    new HtmlWebpackPlugin({
      template: join(paths.public, "index.html"),
      filename: "index.html",
      inject: true,
      minify: isDev ? false : {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    }),
    new MiniCssExtractPlugin({
      filename: isDev ? "[name].[hash].css" : "[name].css",
      chunkFilename: isDev ? "[id].[hash].chunk.css" : "[id].chunk.css"
    }),
    //资源映射
    new webpack.SourceMapDevToolPlugin({
      filename: "[name].js.map",
      exclude: ['vendor.js']
    }),
    //js压缩插件
    new UglifyjsPlugin({
      test: /\.jsx?/,
      include: paths.src,
      exclude: /node_modules/,
      cache: true,
      parallel: os.cpus().length,
      sourceMap: true,
      uglifyOptions: {
        ie8: true,
      }
    }),
  ]
};

优化部分结束,使用yarn run start 打包,一切ok。

 

使用happlypack优化打包速度(webpack4已经不需要它了)

webpack4打包速度已经够快了,故已经不需要happlypack来加速打包了,如果对happypack有兴趣的读者,可以查看这里

https://github.com/amireh/happypack

 

别名配置

到目前为止,项目的搭建已经接近尾声,现在可以开始建文件夹,准备开发了

首先在src下新建components,router,pages,reduxs目录,每个目录是干嘛的上面已经提到了。

现在假设index.jsx要导入这些目录下的jsx或js文件,肯定要这么写

import Xxx from "./components/Button/button.jsx";

我们不仅要写 ./ 还要写具体的文件名如.jsx,那么我们可不可以这么写呢

import Xxx from "components/Button/button"

答案是可以的,不过我们得在webpack下简单配置

在webpack.common.config.js中添加

//webpack.common.config.js
...省略
//添加别名映射
const devPaths = {
  components: resolve(paths.src,"components"),
  pages: resolve(paths.src,"pages"),
  reduxs: resolve(paths.src,"reduxs"),
  router: resolve(paths.src,"router")
};


module.exports = {
  ...省略
  resolve: {
    extensions: ["*",".jsx",".js"], //加上扩展名
    alias:devPaths //别名
  },
  ...省略
  
};

到此为止项目的搭建工作已经基本完成,剩下的后期缺什么补什么。

React,React-router,Redux的介绍和使用

由于篇幅原因,这一部分内容我决定在下一篇博客上讲。

本项目已经放到github里: https://github.com/sundial-dreams/ReactRedux/tree/master (如果对你有帮助,别忘了给我个star哦)

猜你喜欢

转载自blog.csdn.net/daydream13580130043/article/details/83480222