เผยแพร่ จัดส่ง และติดตั้ง JavaScript ที่ทันสมัย เพื่อให้แอปพลิเคชันรวดเร็วขึ้น

ปรับปรุงประสิทธิภาพโดยเปิดใช้การพึ่งพาและเอาต์พุต JavaScript สมัยใหม่

เบราว์เซอร์กว่า 90% เรียกใช้ JavaScript สมัยใหม่ได้ แต่ความแพร่หลายของ JavaScript รุ่นเดิมยังคงเป็นสาเหตุสำคัญของปัญหาด้านประสิทธิภาพบนเว็บในปัจจุบัน

JavaScript ที่ทันสมัย

JavaScript สมัยใหม่ไม่ได้มีลักษณะเป็นโค้ดที่เขียนขึ้นในข้อกำหนด ECMAScript เวอร์ชันที่เจาะจง แต่ใช้ไวยากรณ์ที่เบราว์เซอร์สมัยใหม่ทั้งหมดรองรับ เว็บเบราว์เซอร์สมัยใหม่อย่าง Chrome, Edge, Firefox และ Safari ครองส่วนแบ่งตลาดเบราว์เซอร์มากกว่า 90% ส่วนเบราว์เซอร์อื่นๆ ที่ใช้เครื่องมือแสดงผลเดียวกันมีสัดส่วนอีก 5% ซึ่งหมายความว่าการเข้าชมเว็บทั่วโลก 95% มาจากเบราว์เซอร์ที่รองรับฟีเจอร์ภาษา JavaScript ที่ใช้กันอย่างแพร่หลายมากที่สุดในช่วง 10 ปีที่ผ่านมา ซึ่งรวมถึง

  • คลาส (ES2015)
  • ฟังก์ชันลูกศร (ES2015)
  • เครื่องกำเนิดไฟฟ้า (ES2015)
  • การกําหนดขอบเขตบล็อก (ES2015)
  • การจัดโครงสร้างใหม่ (ES2015)
  • พารามิเตอร์ Rest และ Spread (ES2015)
  • ตัวย่อออบเจ็กต์ (ES2015)
  • Async/await (ES2017)

โดยทั่วไปแล้ว ฟีเจอร์ในข้อกำหนดภาษาเวอร์ชันใหม่จะได้รับการรองรับในเบราว์เซอร์สมัยใหม่อย่างสม่ำเสมอน้อยลง ตัวอย่างเช่น ฟีเจอร์ ES2020 และ ES2021 จำนวนมากรองรับใน 70% ของตลาดเบราว์เซอร์เท่านั้น ซึ่งยังคงเป็นเบราว์เซอร์ส่วนใหญ่ แต่ยังไม่เพียงพอที่จะใช้ฟีเจอร์เหล่านั้นโดยตรงได้อย่างปลอดภัย ซึ่งหมายความว่าแม้ว่า JavaScript "สมัยใหม่" จะเปลี่ยนแปลงอยู่เสมอ แต่ ES2017 ก็มีความสามารถในการทำงานร่วมกับเบราว์เซอร์ได้มากที่สุดและยังมีฟีเจอร์ไวยากรณ์สมัยใหม่ที่ใช้กันโดยทั่วไปส่วนใหญ่ กล่าวคือ ES2017 เป็นไวยากรณ์ที่ใกล้เคียงกับไวยากรณ์สมัยใหม่มากที่สุดในปัจจุบัน

JavaScript เดิม

JavaScript รุ่นเดิมคือโค้ดที่หลีกเลี่ยงการใช้ฟีเจอร์ภาษาข้างต้นทั้งหมดโดยเฉพาะ นักพัฒนาซอฟต์แวร์ส่วนใหญ่เขียนซอร์สโค้ดโดยใช้ไวยากรณ์สมัยใหม่ แต่คอมไพล์ทุกอย่างเป็นไวยากรณ์เดิมเพื่อให้รองรับเบราว์เซอร์ได้มากขึ้น การคอมไพล์เป็นไวยากรณ์เดิมจะเพิ่มการรองรับเบราว์เซอร์ แต่ผลลัพธ์มักจะน้อยกว่าที่เราคาดไว้ ในหลายกรณี การสนับสนุนจะเพิ่มขึ้นจากประมาณ 95% เป็น 98% ในขณะที่มีต้นทุนที่สำคัญ ดังนี้

  • โดยปกติแล้ว JavaScript รุ่นเดิมจะมีขนาดใหญ่กว่าและช้ากว่าโค้ดสมัยใหม่ที่เทียบเท่าประมาณ 20% เครื่องมือที่ไม่เพียงพอและการกําหนดค่าที่ไม่ถูกต้องมักทําให้ช่องว่างนี้กว้างขึ้น

  • ไลบรารีที่ติดตั้งคิดเป็นสัดส่วนถึง 90% ของโค้ด JavaScript ที่ใช้จริงตามปกติ โค้ดไลบรารีจะทำให้เกิดค่าใช้จ่ายเพิ่มเติมของ JavaScript รุ่นเดิมมากกว่าเดิม เนื่องจากมีการใช้ Polyfill และ Helper ซ้ำซึ่งสามารถหลีกเลี่ยงได้ด้วยการเผยแพร่โค้ดสมัยใหม่

JavaScript ที่ทันสมัยใน npm

เมื่อเร็วๆ นี้ Node.js ได้กำหนดมาตรฐานให้กับช่อง "exports" เพื่อกำหนดจุดแรกเข้าของแพ็กเกจ ดังนี้

{
  "exports": "./index.js"
}

โมดูลที่อ้างอิงโดยฟิลด์ "exports" หมายถึง Node เวอร์ชันตั้งแต่ 12.8 ขึ้นไป ซึ่งรองรับ ES2019 ซึ่งหมายความว่าโมดูลที่อ้างอิงโดยใช้ช่อง "exports" สามารถเขียนเป็น JavaScript สมัยใหม่ได้ ผู้ใช้แพ็กเกจต้องถือว่าโมดูลที่มีช่อง "exports" มีโค้ดสมัยใหม่และคอมไพล์อีกครั้งหากจำเป็น

เฉพาะโมเดิร์น

หากต้องการเผยแพร่แพ็กเกจที่มีโค้ดสมัยใหม่และปล่อยให้ผู้ใช้จัดการการแปลงเมื่อใช้แพ็กเกจเป็นข้อกำหนด ให้ใช้เฉพาะช่อง "exports"

{
  "name": "foo",
  "exports": "./modern.js"
}

สมัยใหม่ที่มีการแสดงผลสำรองแบบเดิม

ใช้ช่อง "exports" ร่วมกับ "main" เพื่อเผยแพร่แพ็กเกจโดยใช้โค้ดสมัยใหม่ แต่ให้รวม ES5 + CommonJS สำรองไว้สำหรับเบราว์เซอร์เดิมด้วย

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs"
}

สมัยใหม่ที่มีการเพิ่มประสิทธิภาพสำหรับบัฟเฟิ้ล ESM และบัฟเฟิ้ลเดิม

นอกจากการกำหนดจุดแรกเข้า CommonJS สำรองแล้ว คุณยังใช้ช่อง "module" เพื่อชี้ไปยังกลุ่มเดิมสำรองที่คล้ายกันได้ด้วย แต่ต้องเป็นกลุ่มที่ใช้ไวยากรณ์โมดูล JavaScript (import และ export)

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs",
  "module": "./module.js"
}

เครื่องมือรวมหลายอย่าง เช่น webpack และ Rollup อาศัยช่องนี้เพื่อใช้ประโยชน์จากฟีเจอร์ของโมดูลและเปิดใช้Tree Shaking นี่เป็นกลุ่มเดิมที่ไม่มีโค้ดสมัยใหม่นอกเหนือจากไวยากรณ์ import/export ดังนั้นให้ใช้แนวทางนี้เพื่อจัดส่งโค้ดสมัยใหม่ด้วยตัวเลือกสำรองเดิมที่ยังคงได้รับการเพิ่มประสิทธิภาพสำหรับการรวมกลุ่ม

JavaScript สมัยใหม่ในแอปพลิเคชัน

ไลบรารีของบุคคลที่สามเป็นส่วนประกอบส่วนใหญ่ของโค้ด JavaScript เวอร์ชันที่ใช้งานจริงในเว็บแอปพลิเคชัน แม้ว่าที่ผ่านมา ไลบรารี npm จะเผยแพร่เป็นไวยากรณ์ ES5 เดิม แต่นี่ไม่ใช่สมมติฐานที่ปลอดภัยอีกต่อไปและอาจทำให้การอัปเดตไลบรารีทำให้แอปพลิเคชันของคุณไม่รองรับเบราว์เซอร์

เนื่องจากแพ็กเกจ npm จำนวนมากขึ้นเปลี่ยนไปใช้ JavaScript สมัยใหม่ คุณจึงควรตรวจสอบว่าได้ตั้งค่าเครื่องมือสร้างให้จัดการแพ็กเกจเหล่านั้นแล้ว เป็นไปได้สูงที่แพ็กเกจ npm บางรายการที่คุณใช้อยู่จะใช้ฟีเจอร์ภาษาสมัยใหม่อยู่แล้ว มีตัวเลือกมากมายในการใช้โค้ดสมัยใหม่จาก npm โดยไม่ทำให้แอปพลิเคชันใช้งานไม่ได้ในเบราว์เซอร์รุ่นเก่า แต่แนวคิดทั่วไปคือให้ระบบบิลด์แปลงโค้ดที่ต้องพึ่งพาเป็นเป้าหมายไวยากรณ์เดียวกับซอร์สโค้ด

webpack

ตั้งแต่ webpack 5 เป็นต้นไป คุณสามารถกําหนดค่าไวยากรณ์ที่ webpack จะใช้เมื่อสร้างโค้ดสําหรับกลุ่มและโมดูลได้ การดำเนินการนี้จะไม่แปลงโค้ดหรือทรัพยากร แต่จะส่งผลต่อโค้ด "กาว" ที่ webpack สร้างขึ้นเท่านั้น หากต้องการระบุเป้าหมายการรองรับเบราว์เซอร์ ให้เพิ่มการกำหนดค่า browserslist ลงในโปรเจ็กต์ หรือทำในการกำหนดค่า webpack โดยตรง ดังนี้

module.exports = {
  target: ['web', 'es2017'],
};

นอกจากนี้ คุณยังกำหนดค่า webpack ให้สร้างแพ็กเกจที่เพิ่มประสิทธิภาพซึ่งจะละเว้นฟังก์ชัน Wrapper ที่ไม่จำเป็นเมื่อกำหนดเป้าหมายเป็นสภาพแวดล้อม ES Modules สมัยใหม่ได้ด้วย ซึ่งจะกำหนดค่า webpack ให้โหลดแพ็กเกจที่แยกโค้ดโดยใช้ <script type="module"> ด้วย

module.exports = {
  target: ['web', 'es2017'],
  output: {
    module: true,
  },
  experiments: {
    outputModule: true,
  },
};

มีปลั๊กอิน webpack หลายรายการที่ช่วยให้คอมไพล์และจัดส่ง JavaScript สมัยใหม่ได้ในขณะที่ยังคงรองรับเบราว์เซอร์เดิม เช่น ปลั๊กอิน Optimize และ BabelEsmPlugin

ปลั๊กอิน Optimize

ปลั๊กอิน Optimize คือปลั๊กอิน webpack ที่เปลี่ยนโค้ดที่รวมไว้เป็นชิ้นสุดท้ายจาก JavaScript สมัยใหม่เป็น JavaScript รุ่นเดิมแทนไฟล์ต้นทางแต่ละไฟล์ การตั้งค่าแบบสําเร็จรูปนี้ช่วยให้การกําหนดค่า webpack ถือว่าทุกอย่างเป็น JavaScript สมัยใหม่โดยไม่มีการแยกสาขาพิเศษสําหรับเอาต์พุตหรือไวยากรณ์หลายรายการ

เนื่องจากปลั๊กอิน Optimize ทํางานกับ Bundle แทนที่จะเป็นแต่ละโมดูล ปลั๊กอินจึงประมวลผลโค้ดของแอปพลิเคชันและทรัพยากร Dependency อย่างเท่าเทียมกัน ซึ่งทำให้คุณใช้ npm กับ JavaScript เวอร์ชันใหม่ได้อย่างปลอดภัย เนื่องจากโค้ดของ npm จะได้รับการรวมและแปลงเป็นไวยากรณ์ที่ถูกต้อง นอกจากนี้ ยังทำงานได้เร็วกว่าโซลูชันแบบดั้งเดิมที่มีขั้นตอนการคอมไพล์ 2 ขั้นตอน ทั้งยังสร้างแพ็กเกจแยกต่างหากสำหรับเบราว์เซอร์สมัยใหม่และเบราว์เซอร์เดิม แพ็กเกจทั้ง 2 ชุดได้รับการออกแบบให้โหลดโดยใช้รูปแบบ module/nomodule

// webpack.config.js
const OptimizePlugin = require('optimize-plugin');

module.exports = {
  // ...
  plugins: [new OptimizePlugin()],
};

Optimize Plugin ทำงานได้เร็วและมีประสิทธิภาพกว่าการกำหนดค่า webpack ที่กําหนดเอง ซึ่งโดยทั่วไปจะรวมโค้ดสมัยใหม่และโค้ดเดิมแยกกัน นอกจากนี้ ยังจัดการการเรียกใช้ Babel ให้คุณ และบีบอัดกลุ่มโดยใช้ Terser พร้อมการตั้งค่าที่เหมาะสมแยกต่างหากสำหรับเอาต์พุตสมัยใหม่และเดิม สุดท้าย ระบบจะดึงข้อมูล polyfill ที่จําเป็นสําหรับกลุ่มเดิมที่สร้างขึ้นเป็นสคริปต์เฉพาะเพื่อไม่ให้มีข้อมูลซ้ำหรือโหลดโดยไม่จําเป็นในเบราว์เซอร์รุ่นใหม่

การเปรียบเทียบ: การแปลงโมดูลต้นทาง 2 ครั้งกับการแปลง Bundle ที่สร้างขึ้น

BabelEsmPlugin

BabelEsmPlugin เป็นปลั๊กอิน webpack ที่ทำงานร่วมกับ @babel/preset-env เพื่อสร้างแพ็กเกจที่มีอยู่เวอร์ชันทันสมัยเพื่อส่งโค้ดที่แปลงน้อยลงไปยังเบราว์เซอร์สมัยใหม่ เป็นโซลูชันสำเร็จรูปที่ได้รับความนิยมมากที่สุดสำหรับ module/nomodule ซึ่ง Next.js และ Preact CLI นำมาใช้

// webpack.config.js
const BabelEsmPlugin = require('babel-esm-plugin');

module.exports = {
  //...
  module: {
    rules: [
      // your existing babel-loader configuration:
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [new BabelEsmPlugin()],
};

BabelEsmPlugin รองรับการกำหนดค่า webpack ที่หลากหลาย เนื่องจากจะเรียกใช้บิลด์แยกกัน 2 บิลด์สำหรับแอปพลิเคชัน การคอมไพล์ 2 ครั้งอาจใช้เวลานานขึ้นเล็กน้อยสำหรับแอปพลิเคชันขนาดใหญ่ แต่เทคนิคนี้ช่วยให้ BabelEsmPlugin ผสานรวมเข้ากับการกำหนดค่า webpack ที่มีอยู่ได้อย่างราบรื่น และทำให้เป็นหนึ่งในตัวเลือกที่สะดวกที่สุด

กำหนดค่า babel-loader ให้แปลงโค้ด node_modules

หากคุณใช้ babel-loader โดยไม่ได้ใช้ปลั๊กอินใดปลั๊กอินหนึ่งจาก 2 รายการก่อนหน้านี้ คุณต้องทำตามขั้นตอนสำคัญเพื่อใช้โมดูล npm ของ JavaScript เวอร์ชันใหม่ การกําหนดการกําหนดค่า babel-loader แยกกัน 2 รายการทําให้คอมไพล์ฟีเจอร์ภาษาสมัยใหม่ใน node_modules เป็น ES2017 โดยอัตโนมัติได้ ในขณะที่ยังคงแปลงโค้ดของบุคคลที่หนึ่งด้วยปลั๊กอินและค่ากําหนดล่วงหน้าของ Babel ที่กําหนดไว้ในการกําหนดค่าของโปรเจ็กต์ ซึ่งจะไม่สร้างแพ็กเกจสมัยใหม่และเดิมสำหรับการตั้งค่าโมดูล/ไม่มีโมดูล แต่จะช่วยให้ติดตั้งและใช้แพ็กเกจ npm ที่มี JavaScript สมัยใหม่ได้โดยไม่ทำให้เบราว์เซอร์รุ่นเก่าใช้งานไม่ได้

webpack-plugin-modern-npm ใช้เทคนิคนี้เพื่อคอมไพล์ npm Dependencies ที่มีช่อง "exports" ใน package.json เนื่องจากอาจมีไวยากรณ์สมัยใหม่

// webpack.config.js
const ModernNpmPlugin = require('webpack-plugin-modern-npm');

module.exports = {
  plugins: [
    // auto-transpile modern stuff found in node_modules
    new ModernNpmPlugin(),
  ],
};

หรือจะใช้เทคนิคนี้ด้วยตนเองในการกําหนดค่า webpack ก็ได้ โดยดูหาช่อง "exports" ใน package.json ของข้อบังคับขณะที่ระบบกำลังแก้ไข การติดตั้งใช้งานที่กําหนดเองอาจมีลักษณะดังนี้โดยละเว้นการแคชเพื่อลดความซับซ้อน

// webpack.config.js
module.exports = {
  module: {
    rules: [
      // Transpile for your own first-party code:
      {
        test: /\.js$/i,
        loader: 'babel-loader',
        exclude: /node_modules/,
      },
      // Transpile modern dependencies:
      {
        test: /\.js$/i,
        include(file) {
          let dir = file.match(/^.*[/\\]node_modules[/\\](@.*?[/\\])?.*?[/\\]/);
          try {
            return dir && !!require(dir[0] + 'package.json').exports;
          } catch (e) {}
        },
        use: {
          loader: 'babel-loader',
          options: {
            babelrc: false,
            configFile: false,
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
};

เมื่อใช้แนวทางนี้ คุณจะต้องตรวจสอบว่าเครื่องมือบีบอัดโค้ดของคุณรองรับไวยากรณ์สมัยใหม่ ทั้ง Terser และ uglify-es มีตัวเลือกในการระบุ {ecma: 2017} เพื่อเก็บรักษาและในบางกรณีจะสร้างไวยากรณ์ ES2017 ในระหว่างการบีบอัดและการจัดรูปแบบ

รายงาน

Rollup มีการสนับสนุนการสร้างชุดบันเดิลหลายชุดเป็นส่วนหนึ่งของบิลด์เดียวอยู่แล้ว และจะสร้างโค้ดสมัยใหม่โดยค่าเริ่มต้น ด้วยเหตุนี้ คุณจึงกำหนดค่า Rollup ให้สร้างแพ็กเกจสมัยใหม่และเดิมด้วยปลั๊กอินอย่างเป็นทางการที่คุณมีแนวโน้มที่จะใช้อยู่แล้วได้

@rollup/plugin-babel

หากคุณใช้ Rollup เมธอด getBabelOutputPlugin() (จากปลั๊กอิน Babel อย่างเป็นทางการของ Rollup) จะเปลี่ยนรูปแบบโค้ดในแพ็กเกจที่สร้างขึ้นแทนโมดูลแหล่งที่มาแต่ละรายการ Rollup รองรับการสร้างชุดกลุ่มหลายชุดเป็นส่วนหนึ่งของบิลด์เดียวโดยที่แต่ละชุดมีปลั๊กอินของตัวเอง คุณสามารถใช้วิธีนี้เพื่อสร้างแพ็กเกจที่แตกต่างกันสำหรับเวอร์ชันสมัยใหม่และเวอร์ชันเดิมโดยส่งแต่ละรายการผ่านการกำหนดค่าปลั๊กอินเอาต์พุต Babel ที่แตกต่างกัน ดังนี้

// rollup.config.js
import {getBabelOutputPlugin} from '@rollup/plugin-babel';

export default {
  input: 'src/index.js',
  output: [
    // modern bundles:
    {
      format: 'es',
      plugins: [
        getBabelOutputPlugin({
          presets: [
            [
              '@babel/preset-env',
              {
                targets: {esmodules: true},
                bugfixes: true,
                loose: true,
              },
            ],
          ],
        }),
      ],
    },
    // legacy (ES5) bundles:
    {
      format: 'amd',
      entryFileNames: '[name].legacy.js',
      chunkFileNames: '[name]-[hash].legacy.js',
      plugins: [
        getBabelOutputPlugin({
          presets: ['@babel/preset-env'],
        }),
      ],
    },
  ],
};

เครื่องมือสร้างเพิ่มเติม

Rollup และ webpack มีการกําหนดค่าได้สูง ซึ่งโดยทั่วไปหมายความว่าแต่ละโปรเจ็กต์ต้องอัปเดตการกําหนดค่าเพื่อเปิดใช้ไวยากรณ์ JavaScript สมัยใหม่ในข้อกําหนด นอกจากนี้ยังมีเครื่องมือสร้างระดับสูงขึ้นที่เน้นรูปแบบและค่าเริ่มต้นมากกว่าการกำหนดค่า เช่น Parcel, Snowpack, Vite และ WMR เครื่องมือเหล่านี้ส่วนใหญ่จะถือว่าการพึ่งพา npm อาจมีไวยากรณ์สมัยใหม่ และจะแปลงเป็นไวยากรณ์ระดับที่เหมาะสมเมื่อสร้างสำหรับเวอร์ชันที่ใช้งานจริง

นอกจากปลั๊กอินสำหรับ webpack และ Rollup โดยเฉพาะแล้ว คุณยังเพิ่มแพ็กเกจ JavaScript ที่ทันสมัยที่มีทางเลือกสำรองเดิมลงในโปรเจ็กต์ใดก็ได้โดยใช้การแยกเวอร์ชัน Devolution เป็นเครื่องมือแบบสแตนด์อโลนที่เปลี่ยนรูปแบบเอาต์พุตจากระบบบิลด์เพื่อสร้างตัวแปร JavaScript รุ่นเดิม ซึ่งช่วยให้การรวมและการแปลงรูปแบบใช้เป้าหมายเอาต์พุตสมัยใหม่ได้