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 成人国产精品视频,久久精品视频免费播放,精品视频亚洲

          整合營(yíng)銷服務(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={
          //入口:相對(duì)路徑和絕對(duì)路徑都行
          entry: './src/main.js',
          //輸出
          output: {
          /**
          * path:文件輸出目錄,必須是絕對(duì)路徑
          * path.resolve方法返回一個(gè)絕對(duì)路徑
          * __dirname當(dāng)前文件的文件夾絕對(duì)路徑
          */
          path: path.resolve(__dirname,'dist'),
          //文件輸出的名字 將 js 文件輸出到 static/js 目錄中
          filename: 'static/js/main.js',
          // 自動(dòng)將上次打包目錄資源清空
          clean:true,
          },
          //加載器
          module: {
          rules: [
          ......
          ]
          },
          //插件
          plugins: [
          new ESLintWebpackPlugin({
          // 指定檢查文件的根目錄
          context: path.resolve(__dirname,'src')
          }),
          new HtmlWebpackPlugin({
          // 以 public/index.html 為模板創(chuàng)建文件
          // 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的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 目錄就會(huì)輸出一個(gè) index.html 文件

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

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

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

          3. 啟動(dòng)

          npx webpack serve

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

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

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

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

          項(xiàng)目地址

          github.com/luoxue-vict…

          我把每一課都切成了不同的分支,大家可以根據(jù)課時(shí)一步步學(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í)。

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

          在 index.js 中引入 index.css

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

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

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

          本章主要講解

          1. webpack 基礎(chǔ)配置
          2. 解析 bundle 如何加載模塊
          3. 動(dòng)態(tài) import 加載原理
          4. 使用 webpack-chain 重寫配置
          5. 課時(shí) 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);

          測(cè)試 css

          src/index.css

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

          解析 bundle 如何加載模塊

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

          • bundle 是一個(gè)立即執(zhí)行函數(shù),可以認(rèn)為它是把所有模塊捆綁在一起的一個(gè)巨型模塊。
          • webpack 將所有模塊打包成了 bundle 的依賴,通過一個(gè)對(duì)象注入
          • 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');
           }
          });

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

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

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

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

          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);

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

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

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

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

          動(dòng)態(tài)模塊

          0.boundle.js

          這個(gè)文件就是把我們 import 的模塊放進(jìn)了一個(gè)單獨(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;
           },"\`]
           `);
           }
           }
          ]);

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

          我們?cè)倏聪?dist/bundle.js

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

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

          1. 調(diào)用模塊時(shí)會(huì)在 window 上注冊(cè)一個(gè) webpackJsonp 數(shù)組,window['webpackJsonp']=window['webpackJsonp'] || []
          2. 當(dāng)我們 import時(shí),webpack 會(huì)調(diào)用 __webpack_require__.e(0) 方法,也就是 requireEnsure
          3. webpack 會(huì)動(dòng)態(tài)創(chuàng)建一個(gè) script 標(biāo)簽去加載這個(gè)模塊,加載成功后會(huì)將該模塊注入到 webpackJsonp 中
          4. webpackJsonp.push 會(huì)調(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);
           // ...
           // 動(dòng)態(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) 等價(jià)下面
           .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();

          課時(shí) 1 小結(jié)

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

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

          學(xué)習(xí)一個(gè)工具我們不僅要看懂它的配置,還要對(duì)它的原理一起了解,只有學(xué)到框架的精髓,我們才能應(yīng)對(duì)如今大前端如此迅猛的發(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
          • 自動(dòng)生成 html
          • 項(xiàng)目測(cè)試

          目錄

          │── 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);
           };
          };

          自動(dòng)生成 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)目測(cè)試

          測(cè)試 html 模板

          public/index.html

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

          測(cè)試 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)類型檢查
          • 友好錯(cuò)誤提示插件
          • 配置樣式,style,css、less、sass、postcss 等
          • postcss 配置
          • 編譯前后 css 對(duì)比
          • 配置 autoprefixer
          • 開啟 source map

          目錄

          增加以下文件

          │──── config // 配置目錄
          │ │── babelLoader.js // babel-loader 配置
          │ │── ForkTsChecker.js // ts 靜態(tài)檢查
          │ │── FriendlyErrorsWebpackPlugin.js // 友好錯(cuò)誤提示
          │ └── style
          │──── src // 開發(fā)目錄
          │ │── style
          │ │ │── app.css
          │ │ │── index.less // 測(cè)試 less
          │ │ │── index.scss // 測(cè)試 sass
          │ │ └── index.postcss // 測(cè)試 postcss
          │ └── ts
          │ └── index.ts // 測(cè)試 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)行錯(cuò)誤提示。

          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的編譯添加錯(cuò)誤。
           async: false
           }
           ]);
           // 將TypeScript類型檢查錯(cuò)誤以彈框提示
           // 如果fork-ts-checker-webpack-plugin的async為false時(shí)可以不用
           // 否則建議使用,以方便發(fā)現(xiàn)錯(cuò)誤
           config.plugin('ts-notifier').use(ForkTsCheckerNotifierWebpackPlugin, [
           {
           title: 'TypeScript',
           excludeWarnings: true,
           skipSuccessful: true
           }
           ]);
           };
          };

          友好錯(cuò)誤提示插件

          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 對(duì)比

          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

          自動(dòng)添加 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

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

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

          課時(shí) 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. 使用動(dòng)態(tài) import 或者 require.ensure 語法,在第一節(jié)已經(jīng)講解
          2. 使用 babel-plugin-import 插件按需引入一些組件庫

          Bundle Splitting

          將公共的包提取到 chunk-vendors 里面,比如你require('vue'),webpack 會(huì)將 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 里面,但是在壓縮的時(shí)候就會(huì)被干掉了,因?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
           }
           ]);
           };
          };

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

          本章內(nèi)容

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

          什么是 webpack loader

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

          可選鏈介紹

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

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

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

          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 將整個(gè)文件全部轉(zhuǎn)換成字符串,content 就是整個(gè)文件的內(nèi)容,對(duì) content 進(jìn)行修改,修改完成后再返回一個(gè)新的 content 就完成了一個(gè) loader 轉(zhuǎn)換。是不是很簡(jiǎ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;
           });
          };

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

          本章內(nèi)容

          1. cache-loader
          2. DllPlugin
          3. threadLoader

          cache-loader

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

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

          這里只對(duì) babel 加入了 cache-loader,因?yàn)槲覀兊?ts/js 都是由 babel 進(jìn)行編譯的,不需要對(duì) 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 是將第三方長(zhǎng)期不變的包與實(shí)際項(xiàng)目隔離開來并分別打包,當(dāng)我們 build 時(shí)再將已經(jīng)打包好的 dll 包引進(jìn)來就 ok 了

          我提取了兩個(gè)包 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

          測(cè)試效果變差了 ,線程數(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 })
           }
           }
          }

          課時(shí) 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ù)則會(huì)編譯所有頁面,并清空 dist
          box dev # 默認(rèn)編譯 index 頁面

          參數(shù)

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

          改造為腳手架

          分成三個(gè)命令,進(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) {
           }
           }
          }

          課時(shí) 8:手寫一個(gè)webpack插件

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

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

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

          本節(jié)概要

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

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

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

          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'
           }])
           }
          }

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

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

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

          本章概要

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

          創(chuàng)建 box build:ssr

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

          執(zhí)行 box build:ssr 會(huì)調(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 寫的,避免不了會(huì)用到 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 文件夾作為一個(gè)服務(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 文件也會(huì)越來越大。這會(huì)導(dǎo)致:

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

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

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

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


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

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

          示例代碼 (Webpack 配置):

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

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

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

          示例代碼 (Webpack 配置):

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

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

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

          示例代碼:

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

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

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

          總結(jié)

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

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


          主站蜘蛛池模板: 日韩一区二区在线免费观看| 精品无码成人片一区二区98| 一区国产传媒国产精品| 日韩伦理一区二区| 国产精品亚洲一区二区三区在线| 国产在线一区观看| 亚洲熟妇无码一区二区三区导航| 狠狠综合久久AV一区二区三区| 日本免费一区二区三区最新vr| 亚洲AV日韩综合一区尤物| 日本一区二区三区精品中文字幕 | 无码中文人妻在线一区二区三区| 波多野结衣高清一区二区三区| 精品视频一区二区| 精品一区二区三区在线视频观看 | 91福利国产在线观一区二区| 大屁股熟女一区二区三区| 乱中年女人伦av一区二区| 无码国产亚洲日韩国精品视频一区二区三区 | 国产成人一区二区动漫精品 | 日产一区日产2区| 国产亚洲自拍一区| 中文字幕亚洲一区| 无码人妻精品一区二区三区东京热| 成人免费一区二区三区| 在线精品国产一区二区三区| 国产午夜精品一区理论片飘花| 精品国产一区二区三区久久| 久久久国产精品一区二区18禁| 久久婷婷色一区二区三区| 91在线一区二区| 曰韩人妻无码一区二区三区综合部| 亚洲av日韩综合一区二区三区| 无码中文字幕乱码一区| 日本一区二区三区精品国产| 韩日午夜在线资源一区二区 | 亚洲精品无码一区二区| 日韩精品一区二区三区中文版| 北岛玲在线一区二区| 91久久精一区二区三区大全| 午夜福利av无码一区二区|