Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 91在线精品你懂的免费,国产一在线精品一区在线观看,色综合久久亚洲国产日韩

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          webpack5入門到實(shí)戰(zhàn) (6-處理HTML資源)

          webpack5入門到實(shí)戰(zhàn) (6-處理HTML資源)

          一)處理 Html 資源

          1. 下載包

          npm i html-webpack-plugin -D

          2. 配置

          ? Webpack.config.js

          const path=require('path')
          const ESLintWebpackPlugin=require('eslint-webpack-plugin')
          const HtmlWebpackPlugin=require('html-webpack-plugin')
          
          module.exports={
          //入口:相對路徑和絕對路徑都行
          entry: './src/main.js',
          //輸出
          output: {
          /**
          * path:文件輸出目錄,必須是絕對路徑
          * path.resolve方法返回一個絕對路徑
          * __dirname當(dāng)前文件的文件夾絕對路徑
          */
          path: path.resolve(__dirname,'dist'),
          //文件輸出的名字 將 js 文件輸出到 static/js 目錄中
          filename: 'static/js/main.js',
          // 自動將上次打包目錄資源清空
          clean:true,
          },
          //加載器
          module: {
          rules: [
          ......
          ]
          },
          //插件
          plugins: [
          new ESLintWebpackPlugin({
          // 指定檢查文件的根目錄
          context: path.resolve(__dirname,'src')
          }),
          new HtmlWebpackPlugin({
          // 以 public/index.html 為模板創(chuàng)建文件
          // 新的html文件有兩個特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動引入打包生成的js等資源
          template: path.resolve(__dirname,'public/index.html')
          })
          ],
          //模式
          mode:'development', //開發(fā)模式
          };

          ? public/index.html

          <!DOCTYPE html>
          <html lang="en">
          <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          </head>
          <body>
          <h1>Hello Webpack5</h1>
          <div class="box1"></div>
          <div class="box2"></div>
          <div class="box3"></div>
          <div class="box4"></div>
          <div class="box5"></div>
          </body>
          </html>

          編譯后dist 目錄就會輸出一個 index.html 文件

          (二)開發(fā)服務(wù)器&自動化

          每次寫完代碼都需要手動輸入指令才能編譯代碼,太麻煩了,我們希望一切自動化

          1. 下載包

          npm i webpack-dev-server -D

          2. 配置

          ? webpack.config.js

          const path=require('path')
          const ESLintWebpackPlugin=require('eslint-webpack-plugin')
          const HtmlWebpackPlugin=require('html-webpack-plugin')
          
          module.exports={
          //入口:相對路徑和絕對路徑都行
          entry: './src/main.js',
          //輸出
          output: {
          /**
          * path:文件輸出目錄,必須是絕對路徑
          * path.resolve方法返回一個絕對路徑
          * __dirname當(dāng)前文件的文件夾絕對路徑
          */
          path: path.resolve(__dirname,'dist'),
          //文件輸出的名字 將 js 文件輸出到 static/js 目錄中
          filename: 'static/js/main.js',
          // 自動將上次打包目錄資源清空
          clean:true,
          },
          //加載器
          module: {
          rules: [
          ......
          ]
          },
          //插件
          plugins: [
          new ESLintWebpackPlugin({
          // 指定檢查文件的根目錄
          context: path.resolve(__dirname,'src')
          }),
          new HtmlWebpackPlugin({
          // 以 public/index.html 為模板創(chuàng)建文件
          // 新的html文件有兩個特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動引入打包生成的js等資源
          template: path.resolve(__dirname,'public/index.html')
          })
          ],
          //模式
          mode:'development', //開發(fā)模式
          //開發(fā)服務(wù)器
          devServer:{
          host:'localhost', // 服務(wù)器地址
          port: '3000', //端口
          open: true, //是否自動開啟瀏覽器
          }
          };

          3. 啟動

          npx webpack serve

          注意:使用開發(fā)服務(wù)器時,所有代碼都會在內(nèi)存中編譯打包,并不會輸出到 dist 目錄下

          很多人都或多或少使用過 webpack,但是很少有人能夠系統(tǒng)的學(xué)習(xí) webpack 配置,遇到錯誤的時候就會一臉懵,不知道從哪查起?性能優(yōu)化時也不知道能做什么,網(wǎng)上的優(yōu)化教程是不是符合自己的項(xiàng)目?等一系列問題!本文從最基礎(chǔ)配置一步步到一個完善的大型項(xiàng)目的過程。讓你對 webpack 再也不會畏懼,讓它真正成為你的得力助手!

          本文從下面幾個課題來實(shí)現(xiàn)

          • 課題 1:初探 webpack?探究 webpack 打包原理
          • 課題 2:搭建開發(fā)環(huán)境跟生產(chǎn)環(huán)境
          • 課題 3:基礎(chǔ)配置之loader
          • 課時 4:webpack性能優(yōu)化
          • 課時 5:手寫loader實(shí)現(xiàn)可選鏈
          • 課時 6:webpack編譯優(yōu)化
          • 課時 7:多頁面配置
          • 課時 8:手寫一個webpack插件
          • 課時 9:構(gòu)建 ssr

          項(xiàng)目地址

          github.com/luoxue-vict…

          我把每一課都切成了不同的分支,大家可以根據(jù)課時一步步學(xué)習(xí)



          腳手架

          npm i -g webpack-box

          使用

          webpack-box dev # 開發(fā)環(huán)境
          webpack-box build # 生產(chǎn)環(huán)境
          webpack-box dll # 編譯差分包
          webpack-box dev index # 指定頁面編譯(多頁面)
          webpack-box build index # 指定頁面編譯(多頁面)
          webpack-box build index --report # 開啟打包分析
          webpack-box build:ssr # 編譯ssr
          webpack-box ssr:server # 在 server 端運(yùn)行

          在 package.json 中使用

          {
           "scripts": {
           "dev": "webpack-box dev",
           "build": "webpack-box build",
           "dll": "webpack-box dll",
           "build:ssr": "webpack-box build:ssr",
           "ssr:server": "webpack-box ssr:server"
           }
          }

          使用

          npm run build --report # 開啟打包分析

          擴(kuò)展配置

          box.config.js

          module.exports=function (config) {
           /**
           * @param {object} dll 開啟差分包
           * @param {object} pages 多頁面配置 通過 box run/build index 來使用
           * @param {function} chainWebpack 
           * @param {string} entry 入口
           * @param {string} output 出口 
           * @param {string} publicPath 
           * @param {string} port 
           */
           return {
           entry: 'src/main.js',
           output: 'dist',
           publicPath: '/common/',
           port: 8888,
           dll: {
           venders: ['vue', 'react']
           },
           pages: {
           index: {
           entry: 'src/main.js',
           template: 'public/index.html',
           filename: 'index.html',
           },
           index2: {
           entry: 'src/main.js',
           template: 'public/index2.html',
           filename: 'index2.html',
           }
           },
           chainWebpack(config) {
           }
           }
          }

          課題 1:初探 webpack?探究 webpack 打包原理

          想要學(xué)好 webpack,我們首先要了解 webpack 的機(jī)制,我們先從js加載css開始學(xué)習(xí)。

          我們從下面這個小練習(xí)開始走進(jìn) webpack 吧

          在 index.js 中引入 index.css

          const css=require('./index.css')
          console.log(css)

          css 文件并不能被 js 識別,webpack 也不例外,上述的寫法不出意外會報(bào)錯

          我們?nèi)绾巫?webpack 識別 css 呢,答案就在 webpack 給我們提供了 loader 機(jī)制,可以讓我們通過loader 將任意的文件轉(zhuǎn)成 webpack 可以識別的文件

          本章主要講解

          1. webpack 基礎(chǔ)配置
          2. 解析 bundle 如何加載模塊
          3. 動態(tài) import 加載原理
          4. 使用 webpack-chain 重寫配置
          5. 課時 1 小結(jié)

          webpack 基礎(chǔ)配置

          需要的依賴包

          package.json

          {
           "scripts": {
           "dev": "cross-env NODE_ENV=development webpack", // 開發(fā)環(huán)境
           "build": "cross-env NODE_ENV=production webpack" // 生產(chǎn)環(huán)境
           },
           "dependencies": {
           "cross-env": "^6.0.3", // 兼容各種環(huán)境
           "css-loader": "^3.2.0",
           "rimraf": "^3.0.0", // 刪除文件
           "webpack": "^4.41.2"
           },
           "devDependencies": {
           "webpack-cli": "^3.3.10"
           }
          }

          webpack 基礎(chǔ)配置

          webpack.config.js

          const path=require('path');
          const rimraf=require('rimraf');
          
          // 刪除 dist 目錄
          rimraf.sync('dist');
          
          // webpack 配置
          module.exports={
           entry: './src/index',
           mode: process.env.NODE_ENV,
           output: {
           filename: 'bundle.js',
           path: path.resolve(__dirname, 'dist')
           }
          };

          css 引入到 js

          src/index.js

          const css=require('css-loader!./index.css');
          const a=100;
          console.log(a, css);

          測試 css

          src/index.css

          body {
           width: 100%;
           height: 100vh;
           background-color: orange;
          }

          解析 bundle 如何加載模塊

          我刪掉了一些注釋跟一些干擾內(nèi)容,這樣看起來會更清晰一點(diǎn)

          • bundle 是一個立即執(zhí)行函數(shù),可以認(rèn)為它是把所有模塊捆綁在一起的一個巨型模塊。
          • webpack 將所有模塊打包成了 bundle 的依賴,通過一個對象注入
          • 0 模塊 就是入口
          • webpack 通過 __webpack_require__ 引入模塊
          • __webpack_require__ 就是我們使用的 require,被 webpack 封裝了一層

          dist/bundle.js

          (function(modules) {
           function __webpack_require__(moduleId) {
           if (installedModules[moduleId]) {
           return installedModules[moduleId].exports;
           }
           var module=(installedModules[moduleId]={
           i: moduleId,
           l: false,
           exports: {}
           });
          
           modules[moduleId].call(
           module.exports,
           module,
           module.exports,
           __webpack_require__
           );
          
           module.l=true;
          
           return module.exports;
           }
           return __webpack_require__((__webpack_require__.s=0));
          })({
           './src/index.js': function(module, exports, __webpack_require__) {
           eval(`
           const css=__webpack_require__("./src/style/index.css")
           const a=100;
           console.log(a, css)
           `);
           },
          
           './src/style/index.css': function(module, exports, __webpack_require__) {
           eval(`
           exports=module.exports=__webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false);
           exports.push([module.i, "body {
           width: 100%;
           height: 100vh;
           background-color: orange;
           }", ""]);
           `);
           },
          
           0: function(module, exports, __webpack_require__) {
           module.exports=__webpack_require__('./src/index.js');
           }
          });

          動態(tài) import 加載原理

          如果我們把 index.js 的 require 改成 import 會發(fā)生什么?

          我們知道 import 跟 require 的區(qū)別是,import 是動態(tài)加載只有在用到的時候才會去加載,而 require 只要聲明了就會加載,webpack 遇到了 require 就會把它當(dāng)成一個模塊加載到 bundle的依賴?yán)?/p>

          那么問題來了,如果我們使用了 import 去引用一個模塊,它是如何加載的呢?

          require 改成 import()

          src/index.js

          // const css=require('css-loader!./index.css');
          const css=import('css-loader!./index.css');
          const a=100;
          console.log(a, css);

          動態(tài)加載打包結(jié)果

          除了正常的 bundle 之外,我們還可以看見一個 0.boundle.js

          0.boundle.js 就是我們的動態(tài)加載的 index.css 模塊

          |-- bundle.js
          |-- 0.boundle.js

          動態(tài)模塊

          0.boundle.js

          這個文件就是把我們 import 的模塊放進(jìn)了一個單獨(dú)的 js 文件中

          (window['webpackJsonp']=window['webpackJsonp'] || []).push([
           [0],
           {
           './node_modules/css-loader/dist/runtime/api.js': function(
           module,
           exports,
           __webpack_require__
           ) {
           'use strict';
           eval(`
           ...
           `);
           },
          
           './src/style/index.css': function(module, exports, __webpack_require__) {
           eval(`
           exports=module.exports=__webpack_require__("./node_modules/css-loader/dist/runtime/api.js")(false));
           exports.push([module.i, \`body {
           width: 100%;
           height: 100vh;
           background-color: orange;
           },"\`]
           `);
           }
           }
          ]);

          動態(tài)模塊加載邏輯

          我們再看下 dist/bundle.js

          方便理解,我把大部分代碼和注釋都刪掉了

          原理很簡單,就是利用的 jsonp 的實(shí)現(xiàn)原理加載模塊,只是在這里并不是從 server 拿數(shù)據(jù)而是從其他模塊中

          1. 調(diào)用模塊時會在 window 上注冊一個 webpackJsonp 數(shù)組,window['webpackJsonp']=window['webpackJsonp'] || []
          2. 當(dāng)我們 import時,webpack 會調(diào)用 __webpack_require__.e(0) 方法,也就是 requireEnsure
          3. webpack 會動態(tài)創(chuàng)建一個 script 標(biāo)簽去加載這個模塊,加載成功后會將該模塊注入到 webpackJsonp 中
          4. webpackJsonp.push 會調(diào)用 webpackJsonpCallback 拿到模塊
          5. 模塊加載完(then)再使用 __webpack_require__ 獲取模塊
          (function(modules) {
           function webpackJsonpCallback(data) {
           var chunkIds=data[0];
           var moreModules=data[1];
           var moduleId,
           chunkId,
           i=0,
           resolves=[];
           for (; i < chunkIds.length; i++) {
           chunkId=chunkIds[i];
           if (
           Object.prototype.hasOwnProperty.call(installedChunks, chunkId) &&
           installedChunks[chunkId]
           ) {
           resolves.push(installedChunks[chunkId][0]);
           }
           // 模塊安裝完
           installedChunks[chunkId]=0;
           }
           for (moduleId in moreModules) {
           if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
           modules[moduleId]=moreModules[moduleId];
           }
           }
           if (parentJsonpFunction) parentJsonpFunction(data);
           while (resolves.length) {
           // 執(zhí)行所有 promise 的 resolve 函數(shù)
           resolves.shift()();
           }
           }
          
           function jsonpScriptSrc(chunkId) {
           return __webpack_require__.p + '' + ({}[chunkId] || chunkId) + '.bundle.js';
           }
          
           function __webpack_require__(moduleId) {
           // ...
           }
          
           __webpack_require__.e=function requireEnsure(chunkId) {
           var promises=[];
           // ...
           var script=document.createElement('script');
           var onScriptComplete;
           script.charset='utf-8';
           script.timeout=120;
           script.src=jsonpScriptSrc(chunkId);
          
           onScriptComplete=function(event) {
           // 處理異常,消除副作用
           // ...
           };
           var timeout=setTimeout(function() {
           onScriptComplete({ type: 'timeout', target: script });
           }, 120000);
           script.onerror=script.onload=onScriptComplete;
           document.head.appendChild(script);
           // ...
           // 動態(tài)加載模塊
           return Promise.all(promises);
           };
          
           var jsonpArray=(window['webpackJsonp']=window['webpackJsonp'] || []);
           // 重寫數(shù)組 push 方法
           jsonpArray.push=webpackJsonpCallback;
           jsonpArray=jsonpArray.slice();
           for (var i=0; i < jsonpArray.length; i++)
           webpackJsonpCallback(jsonpArray[i]);
          
           return __webpack_require__((__webpack_require__.s=0));
          })({
           './src/index.js': function(module, exports, __webpack_require__) {
           eval(`
           const css=__webpack_require__.e(0).then(__webpack_require__.t.bind(null, "./src/style/index.css", 7))
           const a=100;
           console.log(a, css)
           `);
           },
           0: function(module, exports, __webpack_require__) {
           eval(`module.exports=__webpack_require__("./src/index.js");`);
           }
          });

          使用 webpack-chain 重寫配置

          我們用 webpack-chain 來寫 webpack 的配置,原因是 webpack-chain 的方式更加靈活

          官方解釋

          webpack-chain 嘗試通過提供可鏈?zhǔn)交蝽樍魇降?API 創(chuàng)建和修改 webpack 配置。API 的 Key 部分可以由用戶指定的名稱引用,這有助于跨項(xiàng)目修改配置方式的標(biāo)準(zhǔn)化。

          const path=require('path');
          const rimraf=require('rimraf');
          const Config=require('webpack-chain');
          const config=new Config();
          const resolve=src=> {
           return path.join(process.cwd(), src);
          };
          
          // 刪除 dist 目錄
          rimraf.sync('dist');
          
          config
           // 入口
           .entry('src/index')
           .add(resolve('src/index.js'))
           .end()
           // 模式
           // .mode(process.env.NODE_ENV) 等價下面
           .set('mode', process.env.NODE_ENV)
           // 出口
           .output.path(resolve('dist'))
           .filename('[name].bundle.js');
          
          config.module
           .rule('css')
           .test(/\.css$/)
           .use('css')
           .loader('css-loader');
          
          module.exports=config.toConfig();

          課時 1 小結(jié)

          至此課時 1 已經(jīng)結(jié)束了,我們主要做了以下事情

          1. webpack 基礎(chǔ)配置
          2. 將 css 通過 css-loader 打包進(jìn) js 中
          3. 解析 bundle 如何加載模塊的
          4. webpack 如何實(shí)現(xiàn)的動態(tài)加載模塊

          學(xué)習(xí)一個工具我們不僅要看懂它的配置,還要對它的原理一起了解,只有學(xué)到框架的精髓,我們才能應(yīng)對如今大前端如此迅猛的發(fā)展。


          課題 2:搭建開發(fā)環(huán)境跟生產(chǎn)環(huán)境

          本章提要:

          • 目錄
          • 實(shí)現(xiàn)可插拔配置
          • 構(gòu)建生產(chǎn)環(huán)境
          • 構(gòu)建開發(fā)環(huán)境(devServer)
          • 提取 css
          • 自動生成 html
          • 項(xiàng)目測試

          目錄

          │── build
          │ │── base.js // 公共部分
          │ │── build.js
          │ └── dev.js
          │── config
          │ │── base.js // 基礎(chǔ)配置
          │ │── css.js // css 配置
          │ │── HtmlWebpackPlugin.js // html 配置
          │ └── MiniCssExtractPlugin.js // 提取css
          │── public // 公共資源
          │ └── index.html // html 模版
          └── src // 開發(fā)目錄
           │── style
           │ └── index.css
           └── main.js // 主入口

          實(shí)現(xiàn)可插拔配置

          package.json

          {
           "scripts": {
           "dev": "cross-env NODE_ENV=development node build/dev.js",
           "build": "cross-env NODE_ENV=production node build/build.js"
           },
           "dependencies": {
           "cross-env": "^6.0.3",
           "css-loader": "^3.2.0",
           "cssnano": "^4.1.10",
           "ora": "^4.0.3",
           "rimraf": "^3.0.0",
           "webpack": "^4.41.2"
           },
           "devDependencies": {
           "extract-text-webpack-plugin": "^3.0.2",
           "html-webpack-plugin": "^3.2.0",
           "mini-css-extract-plugin": "^0.8.0",
           "vue-cli-plugin-commitlint": "^1.0.4",
           "webpack-chain": "^6.0.0",
           "webpack-cli": "^3.3.10",
           "webpack-dev-server": "^3.9.0"
           }
          }

          build/base.js

          const { findSync }=require('../lib');
          const Config=require('webpack-chain');
          const config=new Config();
          const files=findSync('config');
          const path=require('path');
          const resolve=p=> {
           return path.join(process.cwd(), p);
          };
          
          module.exports=()=> {
           const map=new Map();
          
           files.map(_=> {
           const name=_.split('/')
           .pop()
           .replace('.js', '');
           return map.set(name, require(_)(config, resolve));
           });
          
           map.forEach(v=> v());
          
           return config;
          };

          構(gòu)建生產(chǎn)環(huán)境

          build/build.js

          const rimraf=require('rimraf');
          const ora=require('ora');
          const chalk=require('chalk');
          const path=require('path');
          // 刪除 dist 目錄
          rimraf.sync(path.join(process.cwd(), 'dist'));
          
          const config=require('./base')();
          const webpack=require('webpack');
          const spinner=ora('開始構(gòu)建項(xiàng)目...');
          spinner.start();
          
          webpack(config.toConfig(), function(err, stats) {
           spinner.stop();
           if (err) throw err;
           process.stdout.write(
           stats.toString({
           colors: true,
           modules: false,
           children: false,
           chunks: false,
           chunkModules: false
           }) + '\n\n'
           );
          
           if (stats.hasErrors()) {
           console.log(chalk.red('構(gòu)建失敗\n'));
           process.exit(1);
           }
          
           console.log(chalk.cyan('build完成\n'));
          });

          構(gòu)建開發(fā)環(huán)境(devServer)

          build/dev.js

          const config=require('./base')();
          const webpack=require('webpack');
          const chalk=require('chalk');
          const WebpackDevServer=require('webpack-dev-server');
          const port=8080;
          const publicPath='/common/';
          
          config.devServer
           .quiet(true)
           .hot(true)
           .https(false)
           .disableHostCheck(true)
           .publicPath(publicPath)
           .clientLogLevel('none');
          
          const compiler=webpack(config.toConfig());
          // 拿到 devServer 參數(shù)
          const chainDevServer=compiler.options.devServer;
          const server=new WebpackDevServer(
           compiler,
           Object.assign(chainDevServer, {})
          );
          
          ['SIGINT', 'SIGTERM'].forEach(signal=> {
           process.on(signal, ()=> {
           server.close(()=> {
           process.exit(0);
           });
           });
          });
          // 監(jiān)聽端口
          server.listen(port);
          
          new Promise(()=> {
           compiler.hooks.done.tap('dev', stats=> {
           const empty=' ';
           const common=`App running at:
           - Local: http://127.0.0.1:${port}${publicPath}\n`;
           console.log(chalk.cyan('\n' + empty + common));
           });
          });

          提取 css

          config/css.js

          css 提取 loader 配置

          module.exports=(config, resolve)=> {
           return (lang, test)=> {
           const baseRule=config.module.rule(lang).test(test);
           const normalRule=baseRule.oneOf('normal');
           applyLoaders(normalRule);
           function applyLoaders(rule) {
           rule
           .use('extract-css-loader')
           .loader(require('mini-css-extract-plugin').loader)
           .options({
           publicPath: './'
           });
           rule
           .use('css-loader')
           .loader('css-loader')
           .options({});
           }
           };
          };

          css 提取插件 MiniCssExtractPlugin

          config/MiniCssExtractPlugin.js

          const MiniCssExtractPlugin=require('mini-css-extract-plugin');
          
          module.exports=(config, resolve)=> {
           return ()=> {
           config
           .oneOf('normal')
           .plugin('mini-css-extract')
           .use(MiniCssExtractPlugin);
           };
          };

          自動生成 html

          config/HtmlWebpackPlugin.js

          const HtmlWebpackPlugin=require('html-webpack-plugin');
          
          module.exports=(config, resolve)=> {
           return ()=> {
           config.plugin('html').use(HtmlWebpackPlugin, [
           {
           template: 'public/index.html'
           }
           ]);
           };
          };

          項(xiàng)目測試

          測試 html 模板

          public/index.html

          <!DOCTYPE html>
          <html>
           <head>
           <meta charset="UTF-8">
           <title>learn_webpack</title>
           <body></body>
          </html>

          測試 css 模板

          src/style/index.css

          .test {
           width: 200px;
           height: 200px;
           color: red;
           background-color: orange;
          }

          程序入口

          src/main.js

          require('./style/index.css');
          
          const h2=document.createElement('h2');
          h2.className='test';
          h2.innerText='test';
          document.body.append(h2);

          課題 3:基礎(chǔ)配置之loader

          本章提要:

          • 配置 babel
          • 使用 babel 配置 ts
          • ts 靜態(tài)類型檢查
          • 友好錯誤提示插件
          • 配置樣式,style,css、less、sass、postcss 等
          • postcss 配置
          • 編譯前后 css 對比
          • 配置 autoprefixer
          • 開啟 source map

          目錄

          增加以下文件

          │──── config // 配置目錄
          │ │── babelLoader.js // babel-loader 配置
          │ │── ForkTsChecker.js // ts 靜態(tài)檢查
          │ │── FriendlyErrorsWebpackPlugin.js // 友好錯誤提示
          │ └── style
          │──── src // 開發(fā)目錄
          │ │── style
          │ │ │── app.css
          │ │ │── index.less // 測試 less
          │ │ │── index.scss // 測試 sass
          │ │ └── index.postcss // 測試 postcss
          │ └── ts
          │ └── index.ts // 測試 ts
          │── babel.js
          │── postcss.config.js // postcss 配置
          │── tsconfig.json // ts 配置
          └──── dist // 打包后的目錄
           │── app.bundle.js
           │── app.css
           └── index.html

          配置 babel

          config/babelLoader.js

          module.exports=(config, resolve)=> {
           const baseRule=config.module.rule('js').test(/.js│.tsx?$/);
           const babelPath=resolve('babel.js');
           const babelConf=require(babelPath);
           const version=require(resolve('node_modules/@babel/core/package.json'))
           .version;
           return ()=> {
           baseRule
           .use('babel')
           .loader(require.resolve('babel-loader'))
           .options(babelConf({ version }));
           };
          };

          使用 babel 配置 ts

          這里我們使用 babel 插件 @babel/preset-typescript 將 ts 轉(zhuǎn)成 js,并使用ForkTsCheckerWebpackPlugin、ForkTsCheckerNotifierWebpackPlugin 插件進(jìn)行錯誤提示。

          babel.js

          module.exports=function(api) {
           return {
           presets: [
           [
           '@babel/preset-env',
           {
           targets: {
           chrome: 59,
           edge: 13,
           firefox: 50,
           safari: 8
           }
           }
           ],
           [
           '@babel/preset-typescript',
           {
           allExtensions: true
           }
           ]
           ],
           plugins: [
           '@babel/plugin-transform-typescript',
           'transform-class-properties',
           '@babel/proposal-object-rest-spread'
           ]
           };
          };

          ts 靜態(tài)類型檢查

          const ForkTsCheckerWebpackPlugin=require('fork-ts-checker-webpack-plugin');
          const ForkTsCheckerNotifierWebpackPlugin=require('fork-ts-checker-notifier-webpack-plugin');
          
          module.exports=(config, resolve)=> {
           return ()=> {
           config.plugin('ts-fork').use(ForkTsCheckerWebpackPlugin, [
           {
           // 將async設(shè)為false,可以阻止Webpack的emit以等待類型檢查器/linter,并向Webpack的編譯添加錯誤。
           async: false
           }
           ]);
           // 將TypeScript類型檢查錯誤以彈框提示
           // 如果fork-ts-checker-webpack-plugin的async為false時可以不用
           // 否則建議使用,以方便發(fā)現(xiàn)錯誤
           config.plugin('ts-notifier').use(ForkTsCheckerNotifierWebpackPlugin, [
           {
           title: 'TypeScript',
           excludeWarnings: true,
           skipSuccessful: true
           }
           ]);
           };
          };

          友好錯誤提示插件

          config/FriendlyErrorsWebpackPlugin.js

          const FriendlyErrorsWebpackPlugin=require('friendly-errors-webpack-plugin');
          
          module.exports=(config, resolve)=> {
           return ()=> {
           config.plugin('error').use(FriendlyErrorsWebpackPlugin);
           };
          };

          配置樣式,style,css、less、sass、postcss 等

          module.exports=(config, resolve)=> {
           const createCSSRule=(lang, test, loader, options={})=> {
           const baseRule=config.module.rule(lang).test(test);
           const normalRule=baseRule.oneOf('normal');
           normalRule
           .use('extract-css-loader')
           .loader(require('mini-css-extract-plugin').loader)
           .options({
           hmr: process.env.NODE_ENV==='development',
           publicPath: '/'
           });
           normalRule
           .use('css-loader')
           .loader(require.resolve('css-loader'))
           .options({});
           normalRule.use('postcss-loader').loader(require.resolve('postcss-loader'));
           if (loader) {
           const rs=require.resolve(loader);
           normalRule
           .use(loader)
           .loader(rs)
           .options(options);
           }
           };
          
           return ()=> {
           createCSSRule('css', /\.css$/, 'css-loader', {});
           createCSSRule('less', /\.less$/, 'less-loader', {});
           createCSSRule('scss', /\.scss$/, 'sass-loader', {});
           createCSSRule('postcss', /\.p(ost)?css$/);
           };
          };

          postcss 配置

          module.exports={
           plugins: {
           'postcss-px-to-viewport': {
           unitToConvert: 'px',
           viewportWidth: 750,
           unitPrecision: 5,
           propList: ['*'],
           viewportUnit: 'vw',
           fontViewportUnit: 'vw',
           selectorBlackList: [],
           minPixelValue: 1,
           mediaQuery: false,
           replace: true,
           exclude: [],
           landscape: false,
           landscapeUnit: 'vw',
           landscapeWidth: 568
           }
           }
          };

          編譯前后 css 對比

          src/style/index.less

          /* index.less */
          .test {
           width: 300px;
          }

          dist/app.css

          /* index.css */
          .test {
           width: 36.66667vw;
           height: 26.66667vw;
           color: red;
           background-color: orange;
          }
          /* app.css */
          .test {
           font-size: 8vw;
          }
          /* index.less */
          .test {
           width: 40vw;
          }
          
          /* index.scss */
          .test {
           height: 40vw;
          }
          /* index.postcss */
          .test {
           background: green;
           height: 26.66667vw;
          }

          配置 autoprefixer

          自動添加 css 前綴

          postcss.config.js

          module.exports={
           plugins: {
           autoprefixer: {
           overrideBrowserslist: [
           '> 1%',
           'last 3 versions',
           'iOS >=8',
           'Android >=4',
           'Chrome >=40'
           ]
           }
           }
          };

          轉(zhuǎn)換前

          /* index.css */
          .test {
           width: 200px;
           height: 200px;
           color: red;
           display: flex;
           background-color: orange;
          }

          轉(zhuǎn)換后

          /* index.css */
          .test {
           width: 26.66667vw;
           height: 26.66667vw;
           color: red;
           display: -webkit-box;
           display: -webkit-flex;
           display: -ms-flexbox;
           display: flex;
           background-color: orange;
          }

          開啟 source map

          config.devtool('cheap-source-map');
          └── dist
           │── app.bundle.js
           │── app.bundle.js.map
           │── app.css
           │── app.css.map
           └── index.html

          在源文件下會有一行注釋,證明開啟了 sourcemap

          /*# sourceMappingURL=app.css.map*/

          課時 4:webpack性能優(yōu)化

          本章講解

          1. 分離 Manifest
          2. Code Splitting(代碼分割)
          3. Bundle Splitting(打包分割)
          4. Tree Shaking(刪除死代碼)
          5. 開啟 gzip

          分離 Manifest

          module.exports=(config, resolve)=> {
           return ()=> {
           config
           .optimization
           .runtimeChunk({
           name: "manifest"
           })
           }
          }

          Code Splitting

          1. 使用動態(tài) import 或者 require.ensure 語法,在第一節(jié)已經(jīng)講解
          2. 使用 babel-plugin-import 插件按需引入一些組件庫

          Bundle Splitting

          將公共的包提取到 chunk-vendors 里面,比如你require('vue'),webpack 會將 vue 打包進(jìn) chunk-vendors.bundle.js

          module.exports=(config, resolve)=> {
           return ()=> {
           config
           .optimization.splitChunks({
           chunks: 'async',
           minSize: 30000,
           minChunks: 1,
           maxAsyncRequests: 3,
           maxInitialRequests: 3,
           cacheGroups: {
           vendors: {
           name: `chunk-vendors`,
           test: /[\\/]node_modules[\\/]/,
           priority: -10,
           chunks: 'initial'
           },
           common: {
           name: `chunk-common`,
           minChunks: 2,
           priority: -20,
           chunks: 'initial',
           reuseExistingChunk: true
           }
           }
           })
           config.optimization.usedExports(true)
           }
          }

          Tree Shaking

          config/optimization.js

          config.optimization.usedExports(true);

          src/treeShaking.js

          export function square(x) {
           return x * x;
          }
          
          export function cube(x) {
           return x * x * x;
          }

          在 main.js 中只引用了 cube

          import { cube } from './treeShaking';
          
          console.log(cube(2));

          未使用 Tree Shaking

          {
           "./src/treeShaking.js": function(
           module,
           __webpack_exports__,
           __webpack_require__
           ) {
           "use strict";
           __webpack_require__.r(__webpack_exports__);
           __webpack_require__.d(__webpack_exports__, "square", function() {
           return square;
           });
           __webpack_require__.d(__webpack_exports__, "cube", function() {
           return cube;
           });
           function square(x) {
           return x * x;
           }
           function cube(x) {
           return x * x * x;
           }
           }
          }

          使用了 Tree Shaking

          這里只導(dǎo)出了 cube 函數(shù),并沒有將 square 導(dǎo)出去

          當(dāng)然你可以看見 square 函數(shù)還是在 bundle 里面,但是在壓縮的時候就會被干掉了,因?yàn)樗]有被引用

          {
           "./src/treeShaking.js": function(
           module,
           __webpack_exports__,
           __webpack_require__
           ) {
           "use strict";
           __webpack_require__.d(__webpack_exports__, "a", function() {
           return cube;
           });
           function square(x) {
           return x * x;
           }
           function cube(x) {
           return x * x * x;
           }
           }
          }

          只有當(dāng)函數(shù)給定輸入后,產(chǎn)生相應(yīng)的輸出,且不修改任何外部的東西,才可以安全做shaking的操作

          如何使用tree-shaking?

          1. 確保代碼是es6格式,即 export,import
          2. package.json中,設(shè)置 sideEffects
          3. 確保 tree-shaking 的函數(shù)沒有副作用
          4. babelrc中設(shè)置presets [["@babel/preset-env", { "modules": false }]] 禁止轉(zhuǎn)換模塊,交由webpack進(jìn)行模塊化處理
          5. 結(jié)合uglifyjs-webpack-plugin

          其實(shí)在 webpack4 我們根本不需要做這些操作了,因?yàn)?webpack 在生產(chǎn)環(huán)境已經(jīng)幫我們默認(rèn)添加好了,開箱即用!

          開啟 gzip

          CompressionWebpackPlugin.js

          const CompressionWebpackPlugin=require('compression-webpack-plugin');
          
          module.exports=(config, resolve)=> {
           return ()=> {
           config.plugin('CompressionWebpackPlugin').use(CompressionWebpackPlugin, [
           {
           algorithm: 'gzip',
           test: /\.js(\?.*)?$/i,
           threshold: 10240,
           minRatio: 0.8
           }
           ]);
           };
          };

          課時 5:手寫loader實(shí)現(xiàn)可選鏈

          本章內(nèi)容

          1. 什么是 webpack loader
          2. 可選鏈介紹
          3. loader 實(shí)現(xiàn)可選鏈

          什么是 webpack loader

          webpack loader 是 webpack 為了處理各種類型文件的一個中間層,webpack 本質(zhì)上就是一個 node 模塊,它不能處理 js 以外的文件,那么 loader 就幫助 webpack 做了一層轉(zhuǎn)換,將所有文件都轉(zhuǎn)成字符串,你可以對字符串進(jìn)行任意操作/修改,然后返回給 webpack 一個包含這個字符串的對象,讓 webpack 進(jìn)行后面的處理。如果把 webpack 當(dāng)成一個垃圾工廠的話,那么 loader就是這個工廠的垃圾分類!

          可選鏈介紹

          這里并不是純粹意義上的可選鏈,因?yàn)?babel 跟 ts 都已經(jīng)支持了,我們也沒有必要去寫一個完整的可選鏈,只是來加深一下對 loader 的理解, loader 在工作當(dāng)中能幫助我們做什么?

          用途 當(dāng)我們訪問一個對象屬性時不必?fù)?dān)心這個對象是 undefined 而報(bào)錯,導(dǎo)致程序不能繼續(xù)向下執(zhí)行

          解釋 在 ? 之前的所有訪問鏈路都是合法的,不會產(chǎn)生報(bào)錯

          const obj={
           foo: {
           bar: {
           baz: 2
           }
           }
          }
          
          console.log(obj.foo.bar?.baz) // 2
          // 被轉(zhuǎn)成 obj && obj.foo && obj.foo.bar && obj.foo.bar.baz
          console.log(obj.foo.err?.baz) // undefined
          // 被轉(zhuǎn)成 obj && obj.foo && obj.foo.err && obj.foo.err.baz

          loader 實(shí)現(xiàn)可選鏈

          配置loader,options-chain-loader

          config/OptionsChainLoader.js

          module.exports=(config, resolve)=> {
           const baseRule=config.module.rule('js').test(/.js|.tsx?$/);
           const normalRule=baseRule.oneOf('normal');
           return ()=> {
           normalRule
           .use('options-chain')
           .loader(resolve('options-chain-loader'))
           }
          }

          其實(shí)就是正則替換,loader 將整個文件全部轉(zhuǎn)換成字符串,content 就是整個文件的內(nèi)容,對 content 進(jìn)行修改,修改完成后再返回一個新的 content 就完成了一個 loader 轉(zhuǎn)換。是不是很簡單?

          下面的操作意思就是,我們匹配 obj.foo.bar?. 并把它轉(zhuǎn)成 obj && obj.foo && obj.foo.bar && obj.foo.bar.

          options-chain-loader.js

          module.exports=function(content) {
           return content.replace(new RegExp(/([\$_\w\.]+\?\.)/,'g'),function(res) {
           let str=res.replace(/\?\./,'');
           let arrs=str.split('.');
           let strArr=[];
           for(let i=1; i <=arrs.length; i++) {
           strArr.push(arrs.slice(0,i).join('.')); 
           }
           let compile=strArr.join('&&');
           const done=compile + '&&' + str + '.'
           return done;
           });
          };

          課時 6:webpack編譯優(yōu)化

          本章內(nèi)容

          1. cache-loader
          2. DllPlugin
          3. threadLoader

          cache-loader

          cache-loader 主要是將打包好的文件緩存在硬盤的一個目錄里,一般存在 node_modules/.cache下,當(dāng)你再次 build 的時候如果此文件沒有修改就會從緩存中讀取已經(jīng)編譯過的文件,只有有改動的才會被編譯,這樣就大大降低了編譯的時間。尤其是項(xiàng)目越大時越明顯。

          此項(xiàng)目使用前后數(shù)據(jù)對比 3342ms --> 2432ms 效果還是比較明顯

          這里只對 babel 加入了 cache-loader,因?yàn)槲覀兊?ts/js 都是由 babel 進(jìn)行編譯的,不需要對 ts-loader 緩存(我們也沒有用到)

          config/cacheLoader.js

          module.exports=(config, resolve)=> {
           const baseRule=config.module.rule('js').test(/.js|.tsx?$/);
           const babelPath=resolve('babel.js')
           const babelConf=require(babelPath);
           const version=require(resolve('node_modules/@babel/core/package.json')).version
           return ()=> {
           baseRule
           .exclude
           .add(filepath=> {
           // 不緩存 node_modules 下的文件
           return /node_modules/.test(filepath)
           })
           .end()
           .use('cache-loader')
           .loader('cache-loader')
           .options({
           // 緩存位置
           cacheDirectory: resolve('node_modules/.cache/babel')
           })
           }
          }

          DllPlugin

          DllPlugin 是將第三方長期不變的包與實(shí)際項(xiàng)目隔離開來并分別打包,當(dāng)我們 build 時再將已經(jīng)打包好的 dll 包引進(jìn)來就 ok 了

          我提取了兩個包 vue、react,速度差不多提升了 200ms,從 2698ms 到 2377ms

          打包 dll

          build/dll.js

          const path=require("path");
          const dllPath=path.join(process.cwd(), 'dll');
          const Config=require('webpack-chain');
          const config=new Config();
          const webpack=require('webpack')
          const rimraf=require('rimraf');
          const ora=require('ora')
          const chalk=require('chalk')
          const BundleAnalyzerPlugin=require('../config/BundleAnalyzerPlugin')(config)
          
          BundleAnalyzerPlugin()
          config
           .entry('dll')
           .add('vue')
           .add('react')
           .end()
           .set('mode', "production")
           .output
           .path(dllPath)
           .filename('[name].js')
           .library("[name]")
           .end()
           .plugin('DllPlugin')
           .use(webpack.DllPlugin, [{
           name: "[name]",
           path: path.join(process.cwd(), 'dll', 'manifest.json'),
           }])
           .end()
          
          rimraf.sync(path.join(process.cwd(), 'dll'))
          const spinner=ora('開始構(gòu)建項(xiàng)目...')
          spinner.start()
          
          webpack(config.toConfig(), function (err, stats) {
           spinner.stop()
           if (err) throw err
           process.stdout.write(stats.toString({
           colors: true,
           modules: false,
           children: false,
           chunks: false,
           chunkModules: false
           }) + '\n\n')
          
           if (stats.hasErrors()) {
           console.log(chalk.red('構(gòu)建失敗\n'))
           process.exit(1)
           }
           console.log(chalk.cyan('build完成\n'))
          })

          將 dll 包合并

          const webpack=require('webpack')
          
          module.exports=(config, resolve)=> {
           return ()=> {
           config.plugin('DllPlugin')
           .use(webpack.DllReferencePlugin, [{
           context: process.cwd(),
           manifest: require(resolve('dll/manifest.json'))
           }])
           }
          }

          threadLoader

          測試效果變差了 ,線程數(shù)越小編譯速度越快

          config/threadLoader.js

          module.exports=(config, resolve)=> {
           const baseRule=config.module.rule('js').test(/.js|.tsx?$/);
           return ()=> {
           const useThreads=true;
           if (useThreads) {
           const threadLoaderConfig=baseRule
           .use('thread-loader')
           .loader('thread-loader');
           threadLoaderConfig.options({ workers: 3 })
           }
           }
          }

          課時 7:多頁面配置

          注意

          • 棄用 npm run build & npm run dev & npm run dll
          • 改成 box build & box dev & box dll
          • link npm link 將 box 命令鏈接到全局

          本章內(nèi)容

          1. 使用
          2. 改造為腳手架
          3. 多頁面配置

          使用

          box build # 不加參數(shù)則會編譯所有頁面,并清空 dist
          box dev # 默認(rèn)編譯 index 頁面

          參數(shù)

          # index2 是指定編譯的頁面。不會清空 dist
          # report 開啟打包分析
          box build index2 --report 
          box dev index2 --report 

          改造為腳手架

          分成三個命令,進(jìn)行不同操作

          • build
          • dev
          • dll

          bin/box.js

          #!/usr/bin/env node
          
          const chalk=require('chalk')
          const program=require('commander')
          const packageConfig=require('../package.json');
          const { cleanArgs }=require('../lib')
          const path=require('path')
          const __name__=`build,dev,dll`
          
          let boxConf={}
          let lock=false
          
          try {
           boxConf=require(path.join(process.cwd(), 'box.config.js'))()
          } catch (error) { }
          
          program
           .usage('<command> [options]')
           .version(packageConfig.version)
           .command('build [app-page]')
           .description(`構(gòu)建開發(fā)環(huán)境`)
           .option('-r, --report', '打包分析報(bào)告')
           .option('-d, --dll', '合并差分包')
           .action(async (name, cmd)=> {
           const options=cleanArgs(cmd)
           const args=Object.assign(options, { name }, boxConf)
           if (lock) return
           lock=true;
           if (boxConf.pages) {
           Object.keys(boxConf.pages).forEach(page=> {
           args.name=page;
           require('../build/build')(args)
           })
           } else {
           require('../build/build')(args)
           }
           })
          
          program
           .usage('<command> [options]')
           .version(packageConfig.version)
           .command('dev [app-page]')
           .description(`構(gòu)建生產(chǎn)環(huán)境`)
           .option('-d, --dll', '合并差分包')
           .action(async (name, cmd)=> {
           const options=cleanArgs(cmd)
           const args=Object.assign(options, { name }, boxConf)
           if (lock) return
           lock=true;
           require('../build/dev')(args)
           })
          
          program
           .usage('<command> [options]')
           .version(packageConfig.version)
           .command('dll [app-page]')
           .description(`編譯差分包`)
           .action(async (name, cmd)=> {
           const options=cleanArgs(cmd)
           const args=Object.assign(options, { name }, boxConf)
           if (lock) return
           lock=true;
           require('../build/dll')(args)
           })
          
          program.parse(process.argv).args && program.parse(process.argv).args[0];
          program.commands.forEach(c=> c.on('--help', ()=> console.log()))
          
          if (process.argv[2] && !__name__.includes(process.argv[2])) {
           console.log()
           console.log(chalk.red(` 沒有找到 ${process.argv[2]} 命令`))
           console.log()
           program.help()
          }
          
          if (!process.argv[2]) {
           program.help()
          }

          多頁面配置

          box.config.js

          module.exports=function (config) {
           return {
           entry: 'src/main.js', // 默認(rèn)入口
           dist: 'dist', // 默認(rèn)打包目錄
           publicPath: '/',
           port: 8888,
           pages: {
           index: {
           entry: 'src/main.js',
           template: 'public/index.html',
           filename: 'index.html',
           },
           index2: {
           entry: 'src/main.js',
           template: 'public/index2.html',
           filename: 'index2.html',
           }
           },
           chainWebpack(config) {
           }
           }
          }

          課時 8:手寫一個webpack插件

          如果把 webpack 當(dāng)成一個垃圾工廠,loader 就是垃圾分類,將所有垃圾整理好交給 webpack。plugin 就是如何去處理這些垃圾。

          webpack 插件寫起來很簡單,就是你要知道各種各樣的鉤子在什么時候觸發(fā),然后你的邏輯寫在鉤子里面就ok了

          • apply 函數(shù)是 webpack 在調(diào)用 plugin 的時候執(zhí)行的,你可以認(rèn)為它是入口
          • compiler 暴露了和 webpack 整個生命周期相關(guān)的鉤子
          • Compilation 暴露了與模塊和依賴有關(guān)的粒度更小的事件鉤子

          本節(jié)概要

          • 實(shí)現(xiàn)一個 CopyPlugin
          • 使用

          實(shí)現(xiàn)一個 CopyPlugin

          我們今天寫一個 copy 的插件,在webpack構(gòu)建完成之后,將目標(biāo)目錄下的文件 copy 到另一個目錄下

          const fs=require('fs-extra')
          const globby=require('globby')
          
          class CopyDirWebpackPlugin {
           constructor(options) {
           this.options=options;
           }
           apply(compiler) {
           const opt=this.options
           compiler.plugin('done', (stats)=> {
           if (process.env.NODE_ENV==='production') {
           (async ()=>{
           const toFilesPath=await globby([`${opt.to}/**`, '!.git/**'])
           toFilesPath.forEach(filePath=> fs.removeSync(filePath))
           const fromFilesPath=await globby([`${opt.from}/**`])
           fromFilesPath.forEach(fromPath=> {
           const cachePath=fromPath
           fromPath=fromPath.replace('dist', opt.to)
           const dirpaths=fromPath.substring(0, fromPath.lastIndexOf('/'))
           fs.mkdirpSync(dirpaths)
           fs.copySync(cachePath, fromPath)
           })
           console.log(` 完成copy ${opt.from} to ${opt.to}`)
           })()
           }
           });
           }
          }
          
          module.exports=CopyDirWebpackPlugin

          使用

          將打包出來的 dist 目錄下的內(nèi)容 copy 到 dist2 目錄下

          const CopyPlugin=require('../webapck-plugin-copy');
          
          module.exports=({ config })=> {
           return ()=> {
           config.plugin('copy-dist')
           .use(CopyPlugin, [{
           from: 'dist',
           to: 'dist2'
           }])
           }
          }

          課時 9:構(gòu)建 ssr

          ssr 就是服務(wù)端渲染,做 ssr 的好處就是為了處理 spa 的不足,比如 seo 優(yōu)化,服務(wù)端緩存等問題。

          今天主要用 react 的 ssr 來做一個簡單的實(shí)例,讓大家更清晰的入門

          本章概要

          • 創(chuàng)建 box build:ssr
          • 編譯 ssr
          • 編譯 jsx 語法
          • 入口區(qū)分服務(wù)端/客戶端
          • 服務(wù)端渲染
          • 小結(jié)

          創(chuàng)建 box build:ssr

          老規(guī)矩,先來一個 box build:ssr 命令讓程序可以執(zhí)行

          執(zhí)行 box build:ssr 會調(diào)用 build/ssr 執(zhí)行編譯

          program
           .usage('<command> [options]')
           .version(packageConfig.version)
           .command('build:ssr [app-page]')
           .description(`服務(wù)端渲染`)
           .action(async (name, cmd)=> {
           const options=cleanArgs(cmd);
           const args=Object.assign(options, { name }, boxConf);
           if (lock) return;
           lock=true;
           require('../build/ssr')(args);
           });

          編譯 ssr

          與其他的編譯沒有什么區(qū)別,值得住的是

          • target 指定為 umd 模式
          • globalObject 為 this
          • 入口改為 ssr.jsx
          .libraryTarget('umd')
          .globalObject('this')

          build/ssr.js

          module.exports=function(options) {
           const path=require('path');
           const Config=require('webpack-chain');
           const config=new Config();
           const webpack=require('webpack');
           const rimraf=require('rimraf');
           const ora=require('ora');
           const chalk=require('chalk');
           const PATHS={
           build: path.join(process.cwd(), 'static'),
           ssrDemo: path.join(process.cwd(), 'src', 'ssr.jsx')
           };
          
           require('../config/babelLoader')({ config, tsx: true })();
           require('../config/HtmlWebpackPlugin')({
           config,
           options: {
           publicPath: '/',
           filename: 'client.ssr.html'
           }
           })();
          
           config
           .entry('ssr')
           .add(PATHS.ssrDemo)
           .end()
           .set('mode', 'development') // production
           .output.path(PATHS.build)
           .filename('[name].js')
           .libraryTarget('umd')
           .globalObject('this')
           .library('[name]')
           .end();
          
           rimraf.sync(path.join(process.cwd(), PATHS.build));
           const spinner=ora('開始構(gòu)建項(xiàng)目...');
           spinner.start();
          
           webpack(config.toConfig(), function(err, stats) {
           spinner.stop();
           if (err) throw err;
           process.stdout.write(
           stats.toString({
           colors: true,
           modules: false,
           children: false,
           chunks: false,
           chunkModules: false
           }) + '\n\n'
           );
          
           if (stats.hasErrors()) {
           console.log(chalk.red('構(gòu)建失敗\n'));
           process.exit(1);
           }
           console.log(chalk.cyan('build完成\n'));
           });
          };

          編譯 jsx 語法

          因?yàn)槲覀兪怯?react 寫的,避免不了會用到 jsx 語法,所以我們需要在 babel-loader 中使用 @babel/preset-react

          npm i @babel/preset-react -D

          config/babelLoader.js

          if (tsx) {
           babelConf.presets.push('@babel/preset-react');
          }

          入口區(qū)分服務(wù)端/客戶端

          區(qū)分服務(wù)端跟客戶端分別渲染

          const React=require("react");
          const ReactDOM=require("react-dom");
          
          const SSR=<div onClick={()=> alert("hello")}>Hello world</div>;
          
          if (typeof document==="undefined") {
           console.log('在服務(wù)端渲染')
           module.exports=SSR;
          } else {
           console.log('在客戶端渲染')
           const renderMethod=!module.hot ? ReactDOM.render : ReactDOM.hydrate;
           renderMethod(SSR, document.getElementById("app"));
          }

          服務(wù)端渲染

          • 將打包出來的 static 文件夾作為一個服務(wù)
          • 訪問 http://127.0.0.1:8080,進(jìn)入服務(wù)端渲染的頁面
          • 再執(zhí)行一遍 ssr.js 進(jìn)行事件綁定
          module.exports=function (options) {
           const express=require("express");
           const { renderToString }=require("react-dom/server");
           const chalk=require('chalk')
           
           const SSR=require("../static/ssr");
           const port=process.env.PORT || 8080;
          
           server(port);
           
           function server(port) {
           const app=express();
           app.use(express.static("static"));
           app.get("/", (req, res)=>
           res.status(200).send(renderMarkup(renderToString(SSR)))
           );
          
           const empty=' '
           const common=`App running at:
           - Local: http://127.0.0.1:${port}\n`
           console.log(chalk.cyan('\n' + empty + common))
           
           app.listen(port, ()=> process.send && process.send("online"));
           }
           
           function renderMarkup(html) {
           return `<!DOCTYPE html>
           <html>
           <head>
           <title>Webpack SSR Demo</title>
           <meta charset="utf-8" />
           </head>
           <body>
           <div id="app">${html}</div>
           <script src="./ssr.js"></script>
           </body>
           </html>`;
           }
          }

          小結(jié)

          至此 ssr 已經(jīng)結(jié)束了,其實(shí)所有看起來很高大上的技術(shù)都是從一點(diǎn)一滴積累起來的,只要我們明白原理,你也能做出更優(yōu)秀的框架


          本文作者:前端技匠

          原文鏈接:https://juejin.im/post/5de06aa851882572d672c1ad

          ebpack,作為前端構(gòu)建的基石,其代碼分割功能為我們提供了優(yōu)化網(wǎng)站性能的強(qiáng)大武器。今天,我們就來深入剖析 Webpack 代碼分割的奧秘,從入門到實(shí)戰(zhàn),助你打造閃電般加載體驗(yàn)!

          為什么需要代碼分割?

          想象一下,當(dāng)你的網(wǎng)站代碼量越來越龐大,打包后的 JavaScript 文件也會越來越大。這會導(dǎo)致:

          • 頁面加載緩慢: 用戶需要等待很長時間才能看到頁面內(nèi)容。
          • 網(wǎng)絡(luò)請求壓力大: 龐大的 JavaScript 文件會占用大量帶寬。
          • 資源浪費(fèi): 用戶可能只需要使用部分功能,但卻要加載所有代碼。

          Webpack 代碼分割 正是為了解決這些問題而生的!它可以將代碼分割成多個 chunk(代碼塊),按需加載,從而:

          • 加快首屏加載速度: 只加載首屏需要的代碼,其他代碼異步加載。
          • 減少網(wǎng)絡(luò)請求壓力: 只加載當(dāng)前需要的代碼塊。
          • 優(yōu)化緩存利用率: 每個代碼塊獨(dú)立緩存,更新代碼時只需加載更新的代碼塊。

          Webpack 代碼分割實(shí)戰(zhàn):兩種常用方式


          1.entry配置:多入口,多頁面

          entry 配置是最基本的代碼分割方式,適用于多頁面應(yīng)用。通過配置多個入口文件,Webpack 會自動將每個入口文件及其依賴打包成獨(dú)立的 chunk。

          示例代碼 (Webpack 配置):

          module.exports={
            // ...
            entry: {
              index: './src/index.js',
              about: './src/about.js',
            },
            output: {
              // ...
              filename: '[name].bundle.js', //  每個入口文件生成一個獨(dú)立的 bundle
            },
          };
          

          2.SplitChunksPlugin:提取公共代碼,優(yōu)化緩存

          SplitChunksPlugin 是 Webpack 內(nèi)置的代碼分割插件,可以自動分析代碼,提取多個 chunk 中的公共代碼。

          示例代碼 (Webpack 配置):

          module.exports={
            // ...
            optimization: {
              splitChunks: {
                chunks: 'all', //  所有類型的 chunk 都參與分割
                name: 'common', //  公共代碼塊命名為 'common'
              },
            },
          };
          

          進(jìn)階技巧:動態(tài)導(dǎo)入,按需加載

          Webpack 支持使用 import() 語法進(jìn)行動態(tài)導(dǎo)入,實(shí)現(xiàn)按需加載。

          示例代碼:

          //  點(diǎn)擊按鈕時才加載 lodash 庫
          button.addEventListener('click', ()=> {
            import('lodash').then((_)=> {
              console.log(_.join(['Hello', 'Webpack'], ' '));
            });
          });
          

          源碼解析:Webpack 代碼分割原理

          Webpack 會根據(jù)配置,將代碼分割成多個 chunk,每個 chunk 都有一個唯一的 ID。Webpack 會在生成的 HTML 文件中插入 <script> 標(biāo)簽,異步加載其他 chunk。當(dāng)瀏覽器執(zhí)行到 import() 語句時,會根據(jù) chunk ID 加載對應(yīng)的代碼塊。

          總結(jié)

          Webpack 代碼分割是優(yōu)化網(wǎng)站性能的利器,掌握它,你就能像魔法師一樣,將代碼分割成多個部分,按需加載,為用戶帶來閃電般加載體驗(yàn)!

          #頭條創(chuàng)作挑戰(zhàn)賽#


          主站蜘蛛池模板: 爆乳熟妇一区二区三区| 福利国产微拍广场一区视频在线 | 亚洲色无码一区二区三区| 日本免费电影一区二区| 视频一区二区三区在线观看| 亚洲一区二区三区在线观看网站| 中文字幕亚洲一区二区三区| 波多野结衣一区二区免费视频| 日本高清一区二区三区| 国模私拍福利一区二区| 性色A码一区二区三区天美传媒| 中文字幕无码免费久久9一区9| 四虎一区二区成人免费影院网址| 国产福利一区二区三区视频在线| 色狠狠色噜噜Av天堂一区| 日韩AV无码一区二区三区不卡| 久久精品国产AV一区二区三区| 97久久精品一区二区三区| 色妞色视频一区二区三区四区| 精品三级AV无码一区| 亚洲欧洲精品一区二区三区| 在线播放国产一区二区三区 | 日韩三级一区二区| 日韩一区二区在线播放| 午夜影院一区二区| 无码一区二区三区AV免费| 日韩视频一区二区在线观看| 国产伦精品一区二区三区视频小说| 四虎成人精品一区二区免费网站| 国产成人精品视频一区| 福利片免费一区二区三区| 无码少妇一区二区浪潮av| 国产精品一区在线观看你懂的| 日韩精品人妻一区二区中文八零| 国精品无码A区一区二区| 亚洲av一综合av一区| 亚洲一区二区三区无码国产| 天堂va在线高清一区| 国产成人无码aa精品一区 | 中文字幕AV一区二区三区| 少妇一晚三次一区二区三区|