本文的目的是介绍【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项目)
- 3项目脚手架搭建npm, webpack-cli及webpack
- create-react-app webpack4升级webpack5
- vue+webpack4 脚手架搭建
- webpack 配置react脚手架(二):热更新
【webpack系列】从零搭建 webpack4+react 脚手架(五)(webpack搭建react项目)
本章节,我们一起来探讨以下问题:如何对编译后的文件进行gzip压缩,如何让开发环境的控制台输出更加高逼格,如何更好的对编译后的文件进行bundle分析等。
如果你想节省带宽提高网站速度,压缩是一种简单有效的方法。我们模拟一次html的请求,想象一下浏览器和服务器的对话:
- 浏览器:嘿,给我来一个 index.html文件
- 服务器:好的,让我去找找它是不是在~
- 服务器:找到它了,我会返回一个成功的状态码(200 ok),我正在发送文件……
- 浏览器:100kb? 我滴天……等啊……等啊,好的,下载下来了
那现在问题在哪呢?
好吧,这系统是正常的,但是太低效了,坦白讲100kb是一大段的文字,HTML是冗余的,每一个标签都有一个几乎相同的闭合标签。虽然通篇文字都有重复,但是只要你砍掉任何的内容,html都不会正常显示。
当文件太大的时候有什么好办法呢,就是gzip压缩它。
如果我们传输一个替代原始大文件的zip的压缩文件给浏览器,就会节省带宽和下载时间。当浏览器可以下载zip文件,解压,并且渲染给用户。下载很快,页面加载也很快。现在,这个浏览器–服务器的会话大概是这样的:
- 浏览器:嘿,给我来一个index.html,如果要有,给我来一个压缩版的可以吗
- 服务器:容我找找……好,满足你
- 服务器:yep,找到了,马上传给你。
- 浏览器:太棒了,只有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内即可。支持传入一个配置对象,相关说明:
- filename 压缩后的文件名
- algorithm 算法 默认gzip
- test 针对文件的正则表达式规则,符合规则的文件被压缩
- threshold 文件大于这个值的会被压缩
- 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的功能即可。
(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
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的初始化
- npm init 设置各种文件配置
- sudo npm install webpack -g 全局的webpack安装
-
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
- 直接在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
因为脚手架默认是隐藏webpack配置的,所以需要先运行npm run eject或yarn eject暴露配置文件,然后我们就可以开始升级了。
升级需要改动的文件包括分为package.js、 webpack.config.js、webpackDevServer.config 三处。@H_301_3@
package.json 更新
主要是webpack相关包、babel相关包、react相关包、postcss相关包,直接贴对比图了。
@H_301_3@
@H_301_3@
@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修改。
@H_301_3@
IgnorePlugin配置写法更新。
@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 脚手架搭建
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 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脚手架(二):热更新的相关信息,请在本站寻找。
本文标签: