在Node.js中调用webpack+反向代理

目标

1、不使用命令行,由Node.js调用webpack执行打包操作;
2、开启webpack-dev-server代理,并实现数据反向代理。

知识点

1、webpack-dev-server代理proxy:webpack-dev-server可以配置代理;
2、mockjs:生成随机数据;
3、Node.js spawn:Node.js子进程;
4、express:Node.js非常好用的应用框架;
5、gulp-util:可以把webpack的打包提示信息美观地展现。

课程内容

webpack提供了一个Node.js API,能够在Node.js运行时被调用。咱们依然在lesson5的基础上进行代码改造,npm安装express、gulp-util、mockjs

  1. npm install express gulp-util mockjs --save

新建一个app.js

  1. touch app.js

copy进去以下代码

  1. #! /usr/bin/env node --harmony
  2. const fs = require('fs'),
  3. path = require('path'),
  4. gutil = require('gulp-util'),
  5. webpack = require('webpack'),
  6. webpackDevServer = require('webpack-dev-server'),
  7. webpack_config = require('./webpack.config'),
  8. Mock = require('mockjs'),
  9. Random = Mock.Random,
  10. express = require('express'),
  11. app = express();
  12. // 启动webpack-dev-server
  13. const compiler = webpack(webpack_config('development'));
  14. new webpackDevServer(compiler, {
  15. // 这里填写的参数将会插入或者替换webpack.config.js中的原配置
  16. stats: {
  17. colors: true,
  18. chunks: false
  19. },
  20. noInfo: false,
  21. proxy: {
  22. '*': {
  23. target: 'http://localhost:3000',
  24. // 代理到本地3000端口,也就是咱们node运行的端口
  25. }
  26. }
  27. }).listen(8080,function() {console.log('App (dev) is now running on port 8080!');});
  28. // 生成随机数据,测试时非常方便
  29. app.get('/mockData',
  30. function(req, res, next) {
  31. let template = {
  32. "string|1-10": "★",
  33. "number|123.10": 1.123,
  34. 'regexp': /[a-z][A-Z][0-9]/,
  35. 'date': Random.date('yyyy-MM-dd'),
  36. 'image': Random.image(),
  37. 'paragraph': Random.cparagraph()
  38. };
  39. let generateData = Mock.mock(template);
  40. res.send(generateData);
  41. next();
  42. });
  43. // webpack打包
  44. app.get('/build',
  45. function(req, res, next) {
  46. webpack(webpack_config('production'),
  47. function(err, stats) {
  48. gutil.log('[webpack:build]', stats.toString({
  49. chunks: false,
  50. colors: true
  51. }));
  52. if (err) {
  53. throw new gutil.PluginError('webpack:build', err);
  54. }
  55. res.send({
  56. success: true
  57. });
  58. next();
  59. });
  60. });
  61. // 监听3000端口
  62. app.listen(3000,function() {console.log('Proxy Server is running on port 3000!');});

copy以下代码到webpack.config.js,自此不需要用npm命令来打包了,也就可以不需要用到process.env.NODE_ENV的值,为了区分开发和生产环境,这里把webpack.config.js exports成一个函数,并通过传递参数NODE_ENV,最终返回一个配置对象

  1. const path = require('path'),
  2. HtmlWebpackPlugin = require('html-webpack-plugin'),
  3. webpack = require('webpack'),
  4. ExtractTextPlugin = require("extract-text-webpack-plugin"),
  5. OpenBrowserPlugin = require('open-browser-webpack-plugin'),
  6. extractVendor = new ExtractTextPlugin('vendor.css'),
  7. extractStyle = new ExtractTextPlugin('style.css');
  8. module.exports = (NODE_ENV) => {
  9. let config = {
  10. entry: NODE_ENV === 'production' ? {
  11. vendor: ['jquery', 'bootJs'],
  12. app: './webpack.entry'
  13. } : [
  14. 'webpack-dev-server/client?http://localhost:8080',
  15. 'webpack/hot/only-dev-server',
  16. './webpack.entry.js'
  17. ],
  18. output: {
  19. filename: 'bundle.[hash].js',
  20. path: path.resolve(__dirname, './build'),
  21. publicPath: '',
  22. chunkFilename: "chunk.[name].[chunkhash].js"
  23. },
  24. context: __dirname,
  25. module: {
  26. rules: [{
  27. test: /\.css/,
  28. use: NODE_ENV === 'production' ? extractVendor.extract({
  29. fallback: "style-loader",
  30. use: "css-loader?minimize=true"
  31. }) : ['style-loader', 'css-loader?sourceMap']
  32. },
  33. {
  34. test: /\.scss$/,
  35. use: NODE_ENV === 'production' ? extractStyle.extract({
  36. fallback: "style-loader",
  37. use: ["css-loader", "sass-loader"]
  38. }) : ['style-loader', 'css-loader?sourceMap', 'sass-loader?sourceMap']
  39. },
  40. {
  41. test: /\.(jpg|png)$/,
  42. use: ['url-loader?limit=10000&name=img/[name].[ext]']
  43. },
  44. {
  45. test: /\.html$/,
  46. use: 'html-loader?interpolate=require'
  47. },
  48. {
  49. test: /\.js$/,
  50. exclude: /node_modules/,
  51. use: {
  52. loader: 'babel-loader',
  53. options: {
  54. presets: ['env']
  55. }
  56. }
  57. },
  58. {
  59. test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
  60. use: ['file-loader?name=fonts/[name].[ext]']
  61. }]
  62. },
  63. plugins: NODE_ENV === 'production' ? [
  64. new HtmlWebpackPlugin({
  65. template: './src/index.html',
  66. filename: 'index.html'
  67. }),
  68. extractVendor,
  69. extractStyle,
  70. new webpack.DefinePlugin({
  71. 'NODE_ENV': JSON.stringify(NODE_ENV)
  72. }),
  73. new webpack.optimize.UglifyJsPlugin({
  74. compress: {
  75. warnings: true
  76. }
  77. }),
  78. new webpack.optimize.CommonsChunkPlugin({
  79. name: "vendor",
  80. filename: "vendor.js",
  81. minChunks: Infinity,
  82. }),
  83. new webpack.ProvidePlugin({
  84. $: 'jquery',
  85. jQuery: 'jquery'
  86. })
  87. ] : [
  88. new HtmlWebpackPlugin({
  89. template: './src/index.html',
  90. filename: 'index.html'
  91. }),
  92. new webpack.HotModuleReplacementPlugin(),
  93. new webpack.NamedModulesPlugin(),
  94. new webpack.DefinePlugin({
  95. 'NODE_ENV': JSON.stringify(NODE_ENV)
  96. }),
  97. new OpenBrowserPlugin({
  98. url: 'http://localhost:8080/'
  99. }),
  100. new webpack.ProvidePlugin({
  101. $: 'jquery',
  102. jQuery: 'jquery'
  103. })
  104. ],
  105. devServer: {
  106. contentBase: path.resolve(__dirname, 'src'),
  107. hot: true,
  108. noInfo: false
  109. },
  110. devtool: 'source-map',
  111. resolve: {
  112. extensions: ['.js', '.scss', '.html'],
  113. alias: {
  114. 'jquery': 'jquery/dist/jquery.min.js',
  115. 'bootCss': 'bootstrap/dist/css/bootstrap.css',
  116. 'bootJs': 'bootstrap/dist/js/bootstrap.js',
  117. 'fontAwesome': 'font-awesome/css/font-awesome.css'
  118. }
  119. }
  120. };
  121. return config;
  122. };

copy以下代码到body.html中

  1. <h1 class="body-title">this is body</h1>
  2. <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
  3. <ul class="body-list">
  4. <li class="body-list-item" id="body-input">你可以使用BannerPlugin给你的每个打包文件加上你的签名<br>webpack教程<br>by kingvid</li>
  5. </ul>
  6. <button id="body-btn" class="btn">点我</button>
  7. <button id="pack-btn" class="btn">打包</button>
  8. <button id="getData-btn" class="btn">获取本地测试数据</button>
  9. <div id="mockData-con"></div>
  10. <div class="mask"><span><i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i></span></div>

copy以下代码到body.js中

  1. var element = $("#body-input"),
  2. str = element.html(),
  3. progress = 0,
  4. timer = setInterval(() => {
  5. let current = str.substr(progress, 1);
  6. if (current == '<') {
  7. progress = str.indexOf('>', progress) + 1;
  8. } else {
  9. progress++;
  10. }
  11. element.html(str.substring(0, progress) + (progress && 1 ? '_': ''));
  12. if (progress >= str.length) {
  13. clearInterval(timer);
  14. element.html(str.substring(0, progress));
  15. }
  16. },150);
  17. require('../../public/a.js');
  18. $("#body-btn").click(() => {
  19. require.ensure(['../../public/b.js'],
  20. function(require) {
  21. require('../../public/c.js');
  22. },'bc');
  23. });
  24. $("#pack-btn").click(() => {
  25. $(".mask").addClass('active');
  26. $.ajax({
  27. url: '/build',
  28. success: (data) => {
  29. if (data.success) {
  30. alert('打包成功,请查看build文件夹');
  31. } else {
  32. alert('打包失败!');
  33. }
  34. $(".mask").removeClass('active');
  35. }
  36. });
  37. });
  38. $("#getData-btn").click(() => {
  39. $.ajax({
  40. url: '/mockData',
  41. success: (data) => {
  42. let str = '';
  43. str = '<h1>' + data.date + '</h1><ul>';
  44. str += '<li>' + data.string + '</li>' + '<li>' + data.paragraph + '</li>' + '<li>' + data.number + '</li>';
  45. str += '<img src=' + data.image + '>';
  46. $("#mockData-con").append(str);
  47. }
  48. });
  49. });

copy以下代码到body.scss中

  1. .body-title{
  2. border-radius: 4px;
  3. border: solid 1px #ccc;
  4. color: #fff;
  5. background-color: rgba(0,0,0,0.9);
  6. }
  7. .body-list{
  8. margin: auto;
  9. list-style: none;
  10. .body-list-item{
  11. font-size: 20px;
  12. }
  13. }
  14. .mask{
  15. background-color: rgba(255,255,255,0.9);
  16. position: fixed;
  17. top: 0;
  18. left: 0;
  19. width: 100%;
  20. height: 100%;
  21. z-index: 999;
  22. text-align: center;
  23. display: none;
  24. &.active{
  25. display: block;
  26. }
  27. span{
  28. display: inline-block;
  29. margin: auto;
  30. position: absolute;
  31. top: 50%;
  32. left: 50%;
  33. transform: translate(-50%, -50%, 0);
  34. }
  35. }
  36. button.btn{
  37. display: block;
  38. margin: 10px auto;
  39. }
  40. #mockData-con{
  41. text-align: left;
  42. }

运行node app.js,效果如下:
《在Node.js中调用webpack+反向代理》 - 图1
打包后目录如下:
《在Node.js中调用webpack+反向代理》 - 图2

补充

现在开发服务(8080端口)和代理服务(3000端口)是包含在一个进程中了,假设咱们要关闭webpack-dev-server或重启它的话,就会影响到整个进程了,这里咱们可以这样操作,把webpack-dev-server放到Node.js的一个子进程里面去,如果要关闭webpack-dev-server的话,就不会影响到主进程了。
新建一个webpackDevServer.js文件

  1. touch webpackDevServer.js

copy进去以下代码

  1. const webpack = require('webpack');
  2. const webpack_config = require('./webpack.config');
  3. const webpackDevServer = require('webpack-dev-server');
  4. //webpack dev server
  5. const compiler = webpack(webpack_config('development'));
  6. new webpackDevServer(compiler, {
  7. stats: {
  8. colors: true,
  9. chunks: false
  10. },
  11. noInfo: false,
  12. proxy: {
  13. '*': {
  14. target: 'http://localhost:3000',
  15. }
  16. }
  17. }).listen(8080,
  18. function() {
  19. console.log('App (dev) is now running on port 8080!');
  20. });

修改app.js

  1. #! /usr/bin/env node --harmony
  2. const fs = require('fs'),
  3. path = require('path'),
  4. gutil = require('gulp-util'),
  5. webpack = require('webpack'),
  6. webpack_config = require('./webpack.config'),
  7. Mock = require('mockjs'),
  8. Random = Mock.Random,
  9. express = require('express'),
  10. app = express();
  11. // 用子进程开启webpack-dev-server,可按需对子进程关闭和重启
  12. /* 可运行以下语句关闭该子进程
  13. * devServer.stdin.pause();
  14. * devServer.kill();
  15. */
  16. // 如果要重启子进程,运行`devServer = startServer();`
  17. var devServer = startServer();
  18. function startServer() {
  19. let spawn = require('child_process').spawn,
  20. devServer = spawn('node', ['webpackDevServer.js']);
  21. devServer.stdin.setEncoding('utf-8');
  22. devServer.stdout.pipe(process.stdout);
  23. devServer.stdin.end();
  24. devServer.on("error",
  25. function(err) {
  26. console.log("Server error:", err);
  27. });
  28. devServer.on("close",
  29. function(code) {
  30. console.log('webpack-dev-server has shutted down!', code);
  31. });
  32. return devServer;
  33. }
  34. // 生成随机数据,测试时非常方便
  35. app.get('/mockData',
  36. function(req, res, next) {
  37. let template = {
  38. "string|1-10": "★",
  39. "number|123.10": 1.123,
  40. 'regexp': /[a-z][A-Z][0-9]/,
  41. 'date': Random.date('yyyy-MM-dd'),
  42. 'image': Random.image(),
  43. 'paragraph': Random.cparagraph()
  44. };
  45. let generateData = Mock.mock(template);
  46. res.send(generateData);
  47. next();
  48. });
  49. // webpack打包
  50. app.get('/build',
  51. function(req, res, next) {
  52. webpack(webpack_config('production'),
  53. function(err, stats) {
  54. gutil.log('[webpack:build]', stats.toString({
  55. chunks: false,
  56. colors: true
  57. }));
  58. if (err) {
  59. throw new gutil.PluginError('webpack:build', err);
  60. }
  61. res.send({
  62. success: true
  63. });
  64. next();
  65. });
  66. });
  67. // 监听3000端口
  68. app.listen(3000,
  69. function() {
  70. console.log('Proxy Server is running on port 3000!');
  71. });

运行node app.js,效果如上,页面运行顺利。