GVKun编程网logo

【webpack系列】从零搭建 webpack4+react 脚手架(五)(webpack搭建react项目)

5

本文的目的是介绍【webpack系列】从零搭建webpack4+react脚手架的详细情况,特别关注五的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解【webpack

本文的目的是介绍【webpack系列】从零搭建 webpack4+react 脚手架的详细情况,特别关注的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解【webpack系列】从零搭建 webpack4+react 脚手架的机会,同时也不会遗漏关于3项目脚手架搭建npm, webpack-cli及webpack、create-react-app webpack4升级webpack5、vue+webpack4 脚手架搭建、webpack 配置react脚手架(二):热更新的知识。

本文目录一览:

【webpack系列】从零搭建 webpack4+react 脚手架(五)(webpack搭建react项目)

【webpack系列】从零搭建 webpack4+react 脚手架(五)(webpack搭建react项目)

本章节,我们一起来探讨以下问题:如何对编译后的文件进行gzip压缩,如何让开发环境的控制台输出更加高逼格,如何更好的对编译后的文件进行bundle分析等。

1 gzip压缩

如果你想节省带宽提高网站速度,压缩是一种简单有效的方法。我们模拟一次html的请求,想象一下浏览器和服务器的对话:

  1. 浏览器:嘿,给我来一个 index.html文件
  2. 服务器:好的,让我去找找它是不是在~
  3. 服务器:找到它了,我会返回一个成功的状态码(200 ok),我正在发送文件……
  4. 浏览器:100kb? 我滴天……等啊……等啊,好的,下载下来了

那现在问题在哪呢?

好吧,这系统是正常的,但是太低效了,坦白讲100kb是一大段的文字,HTML是冗余的,每一个标签都有一个几乎相同的闭合标签。虽然通篇文字都有重复,但是只要你砍掉任何的内容,html都不会正常显示。

当文件太大的时候有什么好办法呢,就是gzip压缩它。

如果我们传输一个替代原始大文件的zip的压缩文件给浏览器,就会节省带宽和下载时间。当浏览器可以下载zip文件,解压,并且渲染给用户。下载很快,页面加载也很快。现在,这个浏览器–服务器的会话大概是这样的:

  1. 浏览器:嘿,给我来一个index.html,如果要有,给我来一个压缩版的可以吗
  2. 服务器:容我找找……好,满足你
  3. 服务器:yep,找到了,马上传给你。
  4. 浏览器:太棒了,只有10kb,我来解压,并且渲染给用户。

情况很简单:文件越小,下载更快,用户感受更好。下面我们讲讲通过webpack如何对文件进行gzip压缩。

(1)前期准备

开启gzip压缩,需要在webpack配置文件中添加一个plugin,而我们希望把是否开启gzip压缩做成可配置化,也就是说,这个gzip的plugin可以选择添加也可以不添加。
我们在config.js中的build下添加一个配置项,取名为productionGzip

productionGzip:true,

另外,我们还需要修改下webpack.prod.conf.js。首先我们把原来export出来的配置对象放一个变量webpackConfig中,这样,我们可以后续访问的到plugins数组,并且可以追加plugin。稍微修改webpack.prod.conf.js,就像这样:

    const webpackConfig=merge(baseWebpackConfig,{
        mode: production,output: {
            path: config.build.assetsRoot,filename: utils.assetsPath(js/[name].[chunkhash:16].js),chunkFilename: utils.assetsPath(js/[id].[chunkhash].js)
        },module: {
            rules: utils.styleLoaders({
                sourceMap: config.build.productionSourceMap,extract: true,usePostCSS: true,cssModule:config.base.cssModule,cssModuleExcludePath:config.base.cssModuleExcludePath
              })
        },plugins: [
            new HtmlWebpackPlugin({
                template: config.build.index,inject: body,minify: {
                    removeComments: true,collapseWhitespace: true,removeAttributeQuotes: true
                },}),new CleanWebpackPlugin([config.build.assetsRoot],{ allowExternal: true }),//导出css 
            new MiniCssExtractPlugin({
                filename: utils.assetsPath(css/[name].[hash].css),chunkFilename: utils.assetsPath(css/[id].[hash].css),],optimization: {
            minimizer: [
                new UglifyJSPlugin(),new OptimizeCSSAssetsPlugin({
                    cssprocessorOptions: true
                        ? {
                            map: { inline: false }
                        }
                        : {}
                })
            ],splitChunks: {
                chunks: "all",minChunks: 1,cacheGroups: {
                    framework: {
                        priority: 200,test: "framework",name: "framework",enforce: true,reuseExistingChunk: true
                    },vendor: {
                        priority: 10,test: /node_modules/,name: "vendor",reuseExistingChunk: true
                    }
                }
            }
        }
    });

    if (config.build.productionGzip) {
        //添加gzip压缩插件
    }

    module.exports = webpackConfig;
(2)添加gzip压缩插件

安装gzip插件:compression-webpack-plugin

npm i compression-webpack-plugin --save-dev
(3)使用插件

使用只要把CompressionWebpackPlugin的实例对象追加到plugins内即可。支持传入一个配置对象,相关说明:

  1. filename 压缩后的文件名
  2. algorithm 算法 默认gzip
  3. test 针对文件的正则表达式规则,符合规则的文件被压缩
  4. threshold 文件大于这个值的会被压缩
  5. minRatio 压缩率 默认0.8

要配置test,我们在config.js的build属性下新增加一个配置项,取名productionGzipExtensions,是一个数组,定义了那些后缀的文件要被压缩。

productionGzipExtensions: [js,css],

然后这样通过正则表达式:

    new RegExp(\\.( +
        config.build.productionGzipExtensions.join(|) +
    )$)

配置的后缀会符合规则被gzip压缩。
在webpack.prod.conf.js详细的配置如下:

    if (config.build.productionGzip) {
        const CompressionWebpackPlugin = require(compression-webpack-plugin)

        webpackConfig.plugins.push(
          new CompressionWebpackPlugin({
            filename: [path].gz[query],algorithm: gzip,test: new RegExp(
              \\.( +
              config.build.productionGzipExtensions.join(|) +
              )$
            ),threshold: 10240,minRatio: 0.8
          })
        )
      }

执行命令查看:

npm run build

查看编译后生成的js,多了.gz文件,这个就是gzip压缩后的文件,把它们上传到服务器,并且服务器开启gzip的功能即可。

 

bundle分析工具
 
编译后,我们不能直观地知道那些组件被编译到哪些文件中。通过bundle分析工具,可以直观地看清楚这个问题。好处是帮助我们更好地优化代码和改进编译。
(1) 安装webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
(2)配置是否启用的参数

在config.js文件的build属性下增加配置项bundleAnalyzerReport,用来表示是否开启分析。这个变量会不停修改,所以我们希望会在npm命令后面加上--report 就表示最后启动bundle分析,不加就不会启动bundle分析。怎么做?通过process.env.npm_config_report可以获取到npm命令的配置

bundleAnalyzerReport: process.env.npm_config_report
(3)使用

在webpack.prod.conf.js增加如下代码:

    if (config.build.bundleAnalyzerReport) {
      const BundleAnalyzerPlugin = require(webpack-bundle-analyzer).BundleAnalyzerPlugin
      webpackConfig.plugins.push(new BundleAnalyzerPlugin())
    }
(4)执行命令

分别以下2个执行试试看吧

npm run build
npm run build --report

通过增加--report 编译成功后会启动Webpack Bundle Analyzer。默认是http://127.0.0.1:8888。你可以直观看到每个文件有哪些模块被编译进去。

 

美化终端的提示
 
在编译过程中,我们希望在终端能给出提示,编译成功给出我们自定义的一些说明。
(1)ora和chalk

这里需要介绍2个npm库。ora是一个能让你在终端提示状态的库,chalk是方便我们美化输出的文本。
我们先安装这2个库。

npm i --save-dev ora chalk
(2)修改编译的方法

看看我们原先的build方法,打开package.json,在scripts属性下找到build属性,可以看到它的值是
webpack --config build/webpack.prod.conf.js
通过webpack命令在终端去编译,我们无法获取webpack的编译状态,webpack还提供了webpack方法,通过webpack方法编译,编译结束可以执行回调函数。我们需要美化终端的显示,希望在编译中能显示编译的状态,编译结束提示编译成功,很有必要修改成通过webpack方法来编译。
在build目录下新增加一个build.js文件:内容如下:

    const ora = require(ora);
    const chalk = require(chalk)
    const webpack = require(webpack)
    const webpackConfig = require(./webpack.prod.conf);
    const spinner = ora(编译中...\n).start();
    webpack(webpackConfig,function (err,stats) {
        if (err) {
            spinner.fail("编译失败");
            console.log(err);
            return;
        }
        spinner.succeed(编译已结束. \n);

        process.stdout.write(stats.toString({
            colors: true,modules: false,children: false,chunks: false,chunkModules: false
        }) + \n\n);
        console.log(chalk.cyan(  编译成功!\n))
        console.log(chalk.yellow(
              提示: 编译后的文件可以上传并且部署到服务器\n +
              通过file:// 打开index.html不会起作用.\n
        ))
    })

stats是编译结束后webpack回调回来的参数,包含了编译后的文件信息。
修改package.json的build属性:

"build": "node build/build.js",

最后重新执行编译命令看看吧

npm run build

观察终端的输出,是不是漂亮了很多。当然你可以自己定制输出的内容。

3项目脚手架搭建npm, webpack-cli及webpack

3项目脚手架搭建npm, webpack-cli及webpack

1.所有需要使用NPM 的项目都需要使用npm init进行初始化

webpack.github.io/docs/configuration.html

 

使用npm install进行安装的话,会在根目录下产生一个node_modules的文件夹,

所有你npm 的包都会放置于此

-g全局安装

--registry= 指向安装,可以把安装引导到国内环境的安装

2.webpack模块化文件

安装 npm install webpack@1.15.0 --save-dev可以初始化项目中的安装

版本

2.操作

cnpm install webpack@1.15.0 --save-dev

 

 

 

webpack在使用包的时候先使用本地的包,没有的时候才选择使用全局的安装包

webpack使用1.15.0而不使用2.0是因为其中有default的关键字和ie8以下的浏览器有冲突导致出现错误。

 

--save-dev可以把项目的配置记录在package.json中-包括版本

 

3.webpack.config.js

3.loader

 

4.常用命令工具

webpack 常用于调试代码用

wepack -p只用于做线上打包时,会把所有文件都做最小话压缩

webpack --watch 用作监听文件的改变,和自动编译,一般用于开发过程

webpack --config webpack.config.js 改变默认的配置文件位置

删除 rm -r -f node_modules

5.webpack-dev-server

作用:前端开发的服务器

webpack --watch不能刷新浏览器基本不用

 

 

3-4npm和webpack的初始化

  1. npm init 设置各种文件配置
  2. sudo npm install webpack -g 全局的webpack安装
  3. npm install webpack@1.15.0 --save-dev 项目目录安装webpack

    查看版本webpack -v 是否安装成功

    某些情况无法查看的情况需要 npm install webpack-cli -g

 

4.文件的建立

 

app.js

cats = require(''./cats.js'');

console.log(cats);

 

cats.js

var cats = [''dave'', ''henry'', ''martha''];

module.exports = cats;

 

5.打包文件的建立

注意webpack的版本可能会导致打包失败,而版本的额设置需要同步的在package.json中设置好,

然后npm init,再重新安装webpack

webpack ./src/page/index/index.js ./dist/app.js

把index.js打包进app.js中去

较为麻烦

直接把打包文件设置好进行

 

webpack.config.js

 

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

 

module.exports = {

entry: ''./src/page/index/index.js'',

output: {

path: path.resolve(__dirname, ''./dist''),

filename: ''app.bundle.js''

}

};

 

webpack -p进行打包

 

6.webpack对脚本的处理

 

 

1)jquery的使用

npm install jquery --save

然后在index中设置好

 

以上安装方法无法在全局中使用

卸载后重新安装,某些情况下无法供全局使用 npm uninstall jquery --save

  1. 直接在index.html中引入,就成了全局的jq了

然后在index.js设置

 

删除掉jqury npm uninstall jquery --save

2.js多入口的问题 entry

module.exports = {

entry: {

''index'':[''./src/page/index/index.js''],

''login'':[''./src/page/login/index.js''],

},

output: {

path: path.resolve(__dirname, ''./dist''),

filename: ''js/[name].js''

}

};

3.模块化引入jQuery

index.js

var $$ = require(''jquery'');

$$(''body'').html("HELLO JQ*****");

webpack.config.js

externals:{

''jquery'':''window.$''

}

 

此处注意一下两个文件的先后顺序

4.使用CommonsChunkPlugin提取公共代码

特别注意webpack 4已经更改了该插件的支持

关于版本的问题

***1.安装 在全局下安装:npm install webpack -g

安装指定版本:npm install web

pack@<version> -g 例如:npm install webpack@3.4.1 -g

如果只是用来练习全局安装就可以了,一开始装了个4.8.3版本的,node也是最新的npm也好着,一做项目就出现错误,4.*.*版本以上还需要安装另外一个东西,具体的可去webpack官网看。

最后只能把webpack删除,重新装了一个指定版本的,才没有什么问题了。

***2.删除 在全局下删除 npm uninstall webpack -g

最好将项目目录下的node-modules一起删除,否则会有残留文件影响下一次的结果。

 

特别注意在安装全局的webpack和项目的webpack时注意版本的一致性3.6.0

webpack-cli的版本不能比webpack的版本高,不然会出现问题

5.将不同的公共代码打进同一个文件中

webpack.config.js打包入口文件

./src/page/common/index.js''和 ''index'':[''./src/page/index/index.js''],打包进base.js文件中、

 

entry: {

''common'':[''./src/page/common/index.js''],

''index'':[''./src/page/index/index.js''],

''login'':[''./src/page/login/index.js'']

},

脚本

plugins: [

new webpack.optimize.CommonsChunkPlugin({

name: "common",

filename: "js/base.js"

})

]

 

原理-先定义一个公共的module.js》common/index.js 引入require(''../module.js'');》打包

 

create-react-app webpack4升级webpack5

create-react-app webpack4升级webpack5

因为脚手架默认是隐藏webpack配置的,所以需要先运行npm run eject或yarn eject暴露配置文件,然后我们就可以开始升级了。

升级需要改动的文件包括分为package.js、 webpack.config.js、webpackDevServer.config 三处。@H_301_3@

package.json 更新

主要是webpack相关包、babel相关包、react相关包、postcss相关包,直接贴对比图了。

image.png@H_301_3@

image.png@H_301_3@

image.png@H_301_3@@H_301_3@

webpack.config.js文件的更新

部分插件弃用@H_301_3@

PnpWebpackPlugin、ManifestPlugin、WatchMissingNodeModulesPlugin、ModuleScopePlugin、typescriptFormatter,主要删除对应的配置。
​@H_301_3@

部分配置弃用@H_301_3@

output移除futureEmitAssets属性。@H_301_3@

部分配置修改@H_301_3@

output的filename修改。

image.png@H_301_3@
IgnorePlugin配置写法更新。

image.png@H_301_3@
postcss=loader写法更新,修改为下面的样子:@H_301_3@

loader: require.resolve('postcss-loader'),
        options: {
          // Necessary for external CSS imports to work
          // https://github.com/facebook/create-react-app/issues/2677
          postcssOptions: {
            ident: 'postcss',
            config: false,
            plugins:[
              'postcss-flexbugs-fixes',
              [
                'postcss-preset-env',
                {
                  autoprefixer: {
                    flexBox: 'no-2009',
                  },
                  stage: 3,
                },
              ],
              // Adds PostCSS normalize as the reset css with default options,
              // so that it honors browserslist config in package.json
              // which in turn let's users customize the target behavior as per their needs.
              'postcss-normalize',
            ],
              // Adds PostCSS normalize as the reset css with default options,
              // so that it honors browserslist config in package.json
              // which in turn let's users customize the target behavior as per their needs.
          },
         
          sourceMap: isEnvProduction && shouldUseSourceMap,
        },

​@H_301_3@

node配置移动到fallback,参考官网迁移指南。
​@H_301_3@

WorkBoxWebpackPlugin移除importWorkBoxFrom和navigateFallbackBlacklist属性。
​@H_301_3@

ForkTsCheckerWebpackPlugin 移除 formatter 属性。
[@H_301_3@

](https://stackoverflow.com/questions/65018431/webpack-5-uncaught-referenceerror-process-is-not-defined)
​@H_301_3@

部分字段更名@H_301_3@

ManifestPlugin 更名为 WebpackManifestPlugin 。
jsonpFunction 更名为 chunkLoadingGlobal 。@H_301_3@

部分报错处理@H_301_3@

报错process is not defined,解决方法:链接,这里注意一点,如果改完之后报错Cannot find module 'process/browser' ,需要安装node-libs-browser这个依赖。
​@H_301_3@

我这里最终改完的webpack.config.js 完整文件如下,这里因为项目中使用less,所以

vue+webpack4 脚手架搭建

vue+webpack4 脚手架搭建

 1, vue 中 h => h(App) 的含义:

//render: h => h(App) 是下面内容的缩写:

render: function (createElement) {
    return createElement(App);
}
//进一步缩写为(ES6 语法):

render (createElement) {
    return createElement(App);
}
//再进一步缩写为:

render (h){
    return h(App);
}
//按照 ES6 箭头函数的写法,就得到了:

render: h => h(App);

 

2 文章中 vue 组件中如果引入了样式,一定要引入css

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

 

 

----

参考文章:

1: 从零开始配置 webpack4 + vue2.x (一)

2: 从零开始的 webpack4 + vue2.x

webpack 配置react脚手架(二):热更新

webpack 配置react脚手架(二):热更新

下面继续配置 webpack dev server    hot module replacement:

首先配置dev-server     安装     npm i webpack-dev-server -D 

const isDev = process.env.NODE_ENV === ''development''

const config = {
    entry:{},
    output:{},
    plugins:{}
}

if(isDev){
    config.devServer = {
    host: ''0.0.0.0'', //设置 0.0.0.0 的host 可以访问 localhost,127.0.0.1,本季ip来访问
    contentBase: path.join(__dirname, ''../dist''), //因为,devserver是服务打包后的文件,所以和output的path 保持一致即可
    port: ''8888'',
    hot: true,
    overlay: {
      errors: true //server有任何的错误,则在网页中 蒙层加提示
    }
  }
}

module.exports = config;

 

修改json文件: "dev:client": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.config.client.js", 

其中 cross-env 是兼容了win mac linux的NODE_ENV,也是一个安装包:  npm i cross-env -D 

然后 npm run dev:cient 即可以启动服务 localhost:8888;

发现 app.js是无法获取到的,其路径为: http://localhost:8888/public/app.js 可以看出是多了一层 public;

根据server的配置项:contentBase 是把dev-server放在了dist目录下,开启的服务器。则 locahost:8888 相当于 dist目录,而之前output配置的输出文件前面是有路径 public的,所以 dev-server也需要增加这个配置:

if(isDev){
    config.devServer = {
    host: ''0.0.0.0'', //设置 0.0.0.0 的host 可以访问 localhost,127.0.0.1,本季ip来访问
    contentBase: path.join(__dirname, ''../dist''), //因为,devserver是服务打包后的文件,所以和output的path 保持一致即可
    port: ''8888'',
    hot: true,
    overlay: {
      errors: true //server有任何的错误,则在网页中 蒙层加提示
    },
    publicPath: ''/public/'', //增加公共路径,对应着output的 publicPath
    historyApiFallback: {
      index: ''/public/index.html'' //这里是给本地服务器增加功能:因为是单页面应用,如果刷新页面,或者访问不到路由,则跳转到首页
    },
  }
}

注意:一定要把打包生成的dist目录删除掉,在执行 npm run dev:client 这时因为,服务器会先检测本地磁盘是否有dist目录,如果有就会调取这里面的文件!!

===================华丽的分割线

接下来配置 热更新 hot,

Q:webpack-dev-server 已经是热加载,为何还要在 react 项目还要安装 react-hot-loader 呢?

A:其实这两者的更新是有区别的,webpack-dev-server 的热加载是开发人员修改了代码,代码经过打包,重新刷新了整个页面。而 react-hot-loader 不会刷新整个页面,它只替换了修改的代码,做到了页面的局部刷新。但它需要依赖 webpack 的 HotModuleReplacement 热加载插件 (参考文章: react使用react-hot-loader实现局部热更新)

首先在 .babelrc 文件中增加 对react hot更新的配置:

{
  "presets": [
    ["es2015", { "loose": true }],
    "react"
  ],
  "plugins": [ "react-hot-loader/babel"] //使用babel的情况下,添加 react-hot-loader,支持react 热更新
}

 安装包: npm i react-loader@next -D //教程中这里是最新的版本,尚未正式版,开发的时候注意版本

修改app.js 入口文件:

import React from ''react''
import ReactDOM from ''react-dom''
import App from ''./App.jsx''

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

if (module.hot) {
  module.hot.accept(''./App.jsx'', () => {
    const NextApp = require(''./App.jsx'').default  
    ReactDOM.hydrate(<NextApp />,document.getElementById(''root''))
  })
}

// module.hot 监听到 app.jsx发生变化之后,重新获取 app.jsx 为NextApp 然后重新渲染;

修改package.js文件:

const webpack = require(''webpack''); //因为用到了webpack下的包  HotModuleReplacementPlugin
const config ={

}
if(isDev){
    config.entry=[
        ''react-hot-loader/patch'',  //入口文件中要把 hot 打包进去
        path.join(__dirname,''../client/app.js'')
    ],
    config.devServer = {
        host: ''0.0.0.0'', 
        contentBase: path.join(__dirname, ''../dist''), 
        port: ''8888'',
        hot: true, //打开这里
        overlay: {
          errors: true 
        },
        publicPath: ''/public/'', 
        historyApiFallback: {
          index: ''/public/index.html''
        }
    }
    config.plugins.push(new webpack.HotModuleReplacementPlugin) //增加了这里
}

最后还要返回来在 app.js 入口文件中配置:

import React from ''react''
import ReactDOM from ''react-dom''
import { AppContainer } from ''react-hot-loader''
import App from ''./App.jsx''

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

const root = document.getElementById(''root''); 
const render = Component => {
    ReactDOM.hydrate(
        <AppContainer>
            <Component/>
        </AppContainer>,
        root
    )
}

render(App);
if (module.hot) {
  module.hot.accept(''./App.jsx'', () => {
    const NextApp = require(''./App.jsx'').default  
    render(NextApp);
  })
}

这样才能热更新!

=================================服务端更新配置

上面书写了客户端的热更新,并且热更新的文件都存在内存中,所以服务端不能再从 dist文件夹下获取依赖的 js和 html文件,因此,服务端的js文件也需要区分是否是dev模式:

const express = require(''express'')
const ReactSSR = require(''react-dom/server'');
const fs = require(''fs'')
const path = require(''path'')
const app = express();

const isDev = process.env.NODE_ENV === ''development''; //在这里定义
if(!isDev){
    const serverEntry = require(''../dist/server-entry'').default;//引入的是服务端的配置打包后的js文件
    const template = fs.readFileSync(path.join(__dirname, ''../dist/index.html''), ''utf8'')//同步引入客户端打包生成的 html 文件,如果不使用 utf8 则是buffer文件
    app.use(''/public'', express.static(path.join(__dirname, ''../dist''))); //给静态文件指定返回内容,这里给piblic文件夹下的内容返回的是静态文件的dist文件夹
    app.get(''*'', function (req, res) {
      const appString = ReactSSR.renderToString(serverEntry);
      res.send(template.replace(''<!--app-->'',appString)) //用返回的js文件替换掉模板中的<app>,然后发送的是模板文件
    })    
}else{
    //util 文件夹下的 dev.static.js
    const devStatic = require(''./util/dev.static.js'');
   devStatic(app); //之所以这里把 app 传递进去,是因为app是 express(),我们可以在新建的文件中继续使用 app.get、app.send 等函数 } app.listen(
3333, function () { console.log(''server is listening on 3333'') })

根据以上代码可知,把原来的从dist目录下获取文件的代码放在了 不是 dev模式下了,而dev模式下我们放在了 util/dev.static.js 文件下。

根据if else可以看出,在文件 dev.static.js 文件中我们要做的事情是:把静态文件js和模版从内存中提取出来,交给app.get请求 然后 send 出去。

接下来编辑 dev.static.js 文件,首先安装 npm i axios -S

步骤一: 获取内存中的模板html文件

const axios = require(''axios'');// 在浏览器端和服务器端都可以使用 axios


/*在这里从内存中获取模版html,因为每次dev-server启动的是本地的服务,url是固定的;
这样可以根据 dev-server 实时的拿到最新的 模板文件
*/
const getTemplate = () => {
    return new Promise((resolve,reject)=>{
        axios.get(''http://localhost:8888/public/index.html'')
        .then(res => {
            resolve(res.data); //返回的内容放在了 data中
        })
        .catch(reject)
    })
}

module.exports = function (app) {
    app.get("*",function(req,res){

    })
}

步骤二:获取。server-entry.js等bundle文件

const axios = require(''axios'');
//从内存中获取 js等bundle文件,启动webpack,通过webpack打包的结果,获取bundle文件。
const webpack = require(''webpack'');
//通过 config.server.js 文件 获取 输出文件路径等信息
const serverConfig = require(''../../build/webpack.config.server.js'');

const getTemplate = () => {
    return new Promise((resolve,reject)=>{
        axios.get(''http://localhost:8888/public/index.html'')
        .then(res => {
            resolve(res.data); 
        })
        .catch(reject)
    })
}

// 通过webpack的watch方法,监听配置文件中的 entry 入口文件(及其依赖的文件)是否发生变化,一旦变化,就会重新打包(类似于热更新)
const serverCompiler = webpack(serverConfig);
serverCompiler.watch({},(err,status)=>{//status 在终端上显示的信息
    if(err) throw;
    let stats = status.toJson();
    stats.error.forEach(err => console.log(err));
    stats.waring.forEach(warn => console.warn(warn));

    const bundlePath = path.join(
        serverConfig.output.path,
        serverConfig.output.filename
    );// 获取输出文件的路径
})

module.exports = function (app) {
    app.get("*",function(req,res){

    })
}

获取到 生成的 文件名字之后需要在 内存中读取 文件:

要使用 memory-fs,所以要安装 npm i memory-fs -D;

const axios = require(''axios'');
const path = require(''path'');
const webpack = require(''webpack'');
const serverConfig = require(''../../build/webpack.config.server.js'');
// 要使用 memory-fs,所以要安装 npm i memory-fs -D;
// 在内存中读写文件,这样就可以从内存中读取 获取到的文件
const MemoryFs = require(''memory-fs'');

//最后要把得到的js文件,渲染到dom上去,所以要用到
const ReactDomServer = require(''react-dom/server'');

const getTemplate = () => {
    return new Promise((resolve,reject)=>{
        axios.get(''http://localhost:8888/public/index.html'')
        .then(res => {
            resolve(res.data); 
        })
        .catch(reject)
    })
}

//通过module的 constructor 构造方法去创建一个新的 module
const Module = module.constructior

let serverBundle;
const mfs = new MemoryFs;//new 一个 对象;
const serverCompiler = webpack(serverConfig);
serverCompiler.outputFileSystem = mfs; //webpack 提供的配置项,其输出通过mfs内存读写,这里如果写错名字就会写到硬盘中
serverCompiler.watch({},(err,status)=>{
    if(err) throw;
    let stats = status.toJson();
    stats.error.forEach(err => console.log(err));
    stats.waring.forEach(warn => console.warn(warn));

    const bundlePath = path.join(
        serverConfig.output.path,
        serverConfig.output.filename
    );// 获取输出文件的路径
    //通过 mfs 读取文件的路径,就可以得到文件,是 string 类型的文件,无法直接使用
    const bundle = mfs.readFileSync(bundlePath,''utf-8''); //需要传入 编码格式
    const m = new Module(); 
    //动态编译成一个文件,需要给这个文件指定文件名字,否则无法在缓存中进行缓存,下次则拿不到该文件
    m._compile(bundle,''server-entry.js'');//使用module的_compile方法将String的文件,生成一个新的 模块,转换成了真正可以读取的文件
    /*为了在后面的 app.get方法中使用。将生成的文件赋值给全局变量;
    此外,因为是在 watch中执行的,每次依赖的文件更新,输出的文件也会更新*/
    serverBundle = m.exports.default; 
})

module.exports = function (app) {
    app.get("*",function(req,res){
        getTemplate().then(template => {
            const content = ReactDomServer.renderToString(serverBundle);
            res.send(template.replace(''<!--app-->'',content))
        })
    })
}

 

最后在package.json 中定义命令:

{
    "script":{
        "dev:server":"cross-env NODE_ENV = development node server/sever.js"
    }

}

启动客户端和服务器端:npm run dev:client        npm run dev:server;

发现无论是js还是html都返回的一样,所以就想之前 对静态文件的 public中做的区分,但是由于这个是在内存中,所以不同:

安装:  npm i http-proxy-middleware -D   做代理的中间件

 

const axios = require(''axios'');
const path = require(''path'');
const webpack = require(''webpack'');
const serverConfig = require(''../../build/webpack.config.server.js'');
const MemoryFs = require(''memory-fs'');
const ReactDomServer = require(''react-dom/server'');
//引入中间件
const proxy = require(''http-proxy-middleware'');


const getTemplate = () => {
    return new Promise((resolve,reject)=>{
        axios.get(''http://localhost:8888/public/index.html'')
        .then(res => {
            resolve(res.data); 
        })
        .catch(reject)
    })
}

const Module = module.constructior

let serverBundle;
const mfs = new MemoryFs;
const serverCompiler = webpack(serverConfig);
serverCompiler.outputFileSystem = mfs; 
serverCompiler.watch({},(err,status)=>{
    if(err) throw;
    let stats = status.toJson();
    stats.error.forEach(err => console.log(err));
    stats.waring.forEach(warn => console.warn(warn));

    const bundlePath = path.join(
        serverConfig.output.path,
        serverConfig.output.filename
    );
    const bundle = mfs.readFileSync(bundlePath,''utf-8'');
    const m = new Module(); 
    m._compile(bundle,''server-entry.js'');
    serverBundle = m.exports.default; 
})

module.exports = function (app) {
//服务器端端口是 3333;客户端的端口是 8888;
//这里做的代理是,访问当前3333端口的public文件时,代理去请求客户端的 8888端口文件
app.use(''/public'',proxy({ target:''http://localhost:8888'' })) app.get("*",function(req,res){ getTemplate().then(template => { const content = ReactDomServer.renderToString(serverBundle); res.send(template.replace(''<!--app-->'',content)) }) }) }

 

关于【webpack系列】从零搭建 webpack4+react 脚手架的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于3项目脚手架搭建npm, webpack-cli及webpack、create-react-app webpack4升级webpack5、vue+webpack4 脚手架搭建、webpack 配置react脚手架(二):热更新的相关信息,请在本站寻找。

本文标签: