Membuat Bangunan Modern

Halo, Habr!

Setiap browser modern sekarang memungkinkan Anda untuk bekerja dengan Modul ES6 .

Pada pandangan pertama, tampaknya ini adalah hal yang sama sekali tidak berguna - setelah semua, kita semua menggunakan kolektor yang menggantikan impor dengan tantangan internal mereka. Tetapi jika Anda mempelajari spesifikasinya, ternyata berkat mereka Anda dapat memberikan perakitan terpisah untuk browser modern.

Di bawah kucing adalah cerita tentang bagaimana saya bisa mengurangi ukuran aplikasi sebesar 11% tanpa mengurangi browser lama dan saraf saya.



Fitur Modul ES6


ES6 Modul adalah sistem modular yang terkenal dan banyak digunakan untuk semua orang:

/* someFile.js */ import { someFunc } from 'path/to/helpers.js' 

 /* helpers.js */ export function someFunc() { /* ... */ } 

Untuk menggunakan sistem modular ini di browser, Anda perlu menambahkan jenis modul ke setiap tag skrip. Browser yang lebih lama akan melihat bahwa jenisnya berbeda dari teks / javascript, dan tidak akan menjalankan file sebagai JavaScript.

 <!--        ES6 Modules --> <script type="module" src="/path/to/someFile.js"></script> 

Spesifikasi ini juga memiliki atribut nomodule untuk tag skrip. Browser yang mendukung Modul ES6 akan mengabaikan skrip ini, dan browser yang lebih lama akan mengunduhnya dan menjalankannya.

 <!--       --> <script nomodule src="/path/to/someFileFallback.js"></script> 

Ternyata Anda bisa membuat dua rakitan: yang pertama dengan tipe modul untuk browser modern (Modern Build), dan yang lainnya dengan nomodule untuk browser lama (Fallback build):

 <script type="module" src="/path/to/someFile.js"></script> <script nomodule src="/path/to/someFileFallback.js"></script> 

Mengapa itu perlu?


Sebelum mengirim proyek ke produksi, kita harus:

  • Tambahkan polyphiles.
  • Ubah kode modern menjadi yang lebih lama.

Dalam proyek saya, saya mencoba untuk mendukung jumlah browser maksimum, kadang-kadang bahkan IE 10 . Oleh karena itu, daftar polyfile saya terdiri dari hal-hal dasar seperti es6.promise, es6.object.values, dll. Tetapi browser dengan Modul ES6 mendukung semua metode ES6, dan mereka tidak membutuhkan kilobyte polyfill tambahan.

Transpilasi juga meninggalkan tanda nyata pada ukuran file: babel / preset-env menggunakan 25 transformer untuk menutupi sebagian besar browser, yang masing-masing meningkatkan ukuran kode. Pada saat yang sama, untuk browser dengan dukungan Modul ES6 , jumlah transformer berkurang menjadi 9.

Jadi, dalam perakitan untuk browser modern, kita dapat menghapus polyfile yang tidak perlu dan mengurangi jumlah transformer, yang akan sangat mempengaruhi ukuran file yang dihasilkan!

Bagaimana cara menambahkan polyfiles


Sebelum menyiapkan Modern Build untuk browser modern, perlu disebutkan bagaimana saya menambahkan polyfill ke proyek.



Biasanya, proyek menggunakan core-js untuk menambahkan semua kemungkinan polyfill.

Tentu saja, Anda tidak ingin semua 88 Kbytes polyfile dari perpustakaan ini, tetapi hanya yang diperlukan untuk daftar browser Anda. Fitur ini tersedia menggunakan babel / preset-env dan opsi useBuiltIns-nya. Jika Anda mengaturnya untuk entri, maka impor core-js akan diganti dengan impor modul individual yang dibutuhkan browser Anda:

 /* .babelrc.js */ module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'entry', /* ... */ }] ], /* ... */ }; 

 /*   */ import 'core-js'; 

 /*   */ import "core-js/modules/es6.array.copy-within"; import "core-js/modules/es6.array.fill"; import "core-js/modules/es6.array.find"; /*   -  */ 

Tetapi dengan transformasi seperti itu kami hanya menyingkirkan sebagian dari polifil yang sangat tua yang tidak perlu. Kami masih memiliki polyphiles untuk TypedArray, WeakMap dan hal-hal aneh lainnya yang tidak pernah digunakan dalam proyek.

Untuk benar-benar mengatasi masalah ini, untuk opsi useBuiltIns saya menetapkan nilai untuk penggunaan. Pada tahap kompilasi, babel / preset-env akan menganalisis file untuk menggunakan fitur yang tidak tersedia di browser yang dipilih, dan menambahkan polyfiles ke mereka:

 /* .babelrc.js */ module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', /* ... */ }] ], /* ... */ }; 

 /*   */ function sortStrings(strings) { return strings.sort(); } function createResolvedPromise() { return Promise.resolve(); } 

 /*   */ import "core-js/modules/es6.array.sort"; import "core-js/modules/es6.promise"; function sortStrings(strings) { return strings.sort(); } function createResolvedPromise() { return Promise.resolve(); } 

Pada contoh di atas, babel / preset-env menambahkan polifile ke fungsi sortir. Anda tidak dapat menemukan dalam JavaScript jenis objek apa yang akan diteruskan ke fungsi - itu akan menjadi array atau objek kelas dengan fungsi sortir, tetapi babel / preset-env memilih skenario terburuk dan memasukkan polyfile.

Situasi ketika babel / preset-env salah terjadi sepanjang waktu. Untuk menghapus polifil yang tidak perlu, periksa dari waktu ke waktu mana yang Anda impor, dan hapus yang tidak perlu menggunakan opsi kecualikan:

 /* .babelrc.js */ module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', //   ,  ,     debug: true, //      exclude: ['es6.regexp.to-string', 'es6.number.constructor'], /* ... */ }] ], /* ... */ }; 

Saya tidak mempertimbangkan modul regenerator-runtime, karena saya menggunakan fast-async ( dan saya menyarankan semua orang ).

Buat Bangun Modern


Ayo siapkan Modern Build.

Pastikan bahwa kami memiliki file daftar browser di proyek yang menjelaskan semua browser yang diperlukan:

 /* .browserslistrc */ > 0.5% IE 10 

Tambahkan variabel lingkungan BROWSERS_ENV selama pembuatan, yang dapat mengambil nilai fallback (untuk Fallback Build) dan modern (untuk Modern Build):

 /* package.json */ { "scripts": { /* ... */ "build": "NODE_ENV=production webpack /.../", "build:fallback": "BROWSERS_ENV=fallback npm run build", "build:modern": "BROWSERS_ENV=modern npm run build" }, /* ... */ } 

Sekarang ubah konfigurasi babel / preset-env. Untuk menentukan browser yang didukung dalam preset ada opsi target. Dia memiliki singkatan khusus - esmodules. Saat menggunakannya, babel / preset-env akan secara otomatis mengganti browser yang mendukung modul ES6 .

 /* .babelrc.js */ const isModern = process.env.BROWSERS_ENV === 'modern'; module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', //  Modern Build     ES6 modules, //   Fallback Build     .browsersrc targets: isModern ? { esmodules: true } : undefined, /* ... */ }] ], /* ... */ ], }; 

Babel / preset-env akan melakukan semua pekerjaan untuk kita lebih jauh: pilih hanya polifil dan transformasi yang diperlukan.

Sekarang kita dapat membangun proyek untuk browser modern atau lama hanya dengan perintah dari konsol!

Bind Modern dan Fallback Build


Langkah terakhir adalah menggabungkan Modern dan Fallback Builds menjadi satu.

Saya berencana untuk membuat struktur proyek seperti itu:

 //     dist/ //  html- index.html //   Modern Build' modern/ ... //   Fallback Build' fallback/ ... 

Di index.html akan ada tautan ke file javascript yang diperlukan dari kedua majelis:

 /* index.html */ <html> <head> <!-- ... --> </head> <body> <!-- ... --> <script type="module" src="/modern/js/app.540601d23b6d03413d5b.js"></script> <script nomodule src="/fallback/js/app.4d03e1af64f68111703e.js"></script> </body> </html> 

Langkah ini dapat dibagi menjadi tiga bagian:

  1. Build Modern dan Fallback Build dalam direktori yang berbeda.
  2. Mendapatkan informasi tentang jalur ke file javascript yang diperlukan.
  3. Membuat index.html dengan tautan ke semua file javascript.

Ayo mulai!

Build Modern dan Fallback Build dalam direktori yang berbeda


Untuk memulainya, mari ambil langkah termudah - kami akan mengumpulkan Modern dan Fallback Build di direktori yang berbeda di dalam direktori dist.

Sangat tidak mungkin untuk menentukan direktori yang diinginkan untuk output.path, karena kita memerlukan webpack untuk memiliki path ke file relatif ke direktori dist (index.html ada di direktori ini, dan semua dependensi lainnya akan dipompa relatif terhadap itu).

Buat fungsi khusus untuk menghasilkan jalur file:

 /* getFilePath.js */ /*   ,       */ const path = require('path'); const isModern = process.env.BROWSERS_ENV === 'modern'; const prefix = isModern ? 'modern' : 'fallback'; module.exports = relativePath => ( path.join(prefix, relativePath) ); 

 /* webpack.prod.config.js */ const getFilePath = require('path/to/getFilePath'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { mode: 'production', output: { path: 'dist', filename: getFilePath('js/[name].[contenthash].js'), }, plugins: [ new MiniCssExtractPlugin({ filename: getFilePath('css/[name].[contenthash].css'), }), /* ... */ ], /* ... */ } 

Proyek ini mulai berkumpul di direktori yang berbeda untuk Modern dan Fallback Build.

Mendapatkan informasi tentang jalur ke file javascript yang diperlukan


Untuk mendapatkan informasi tentang file yang dikumpulkan, hubungkan plugin webpack-manifest-plugin. Di akhir perakitan, ia akan menambahkan file manifest.json dengan data di jalur ke file:

 /* webpack.prod.config.js */ const getFilePath = require('path/to/getFilePath'); const WebpackManifestPlugin = require('webpack-manifest-plugin'); module.exports = { mode: 'production', plugins: [ new WebpackManifestPlugin({ fileName: getFilePath('manifest.json'), }), /* ... */ ], /* ... */ } 

Sekarang kami memiliki informasi tentang file yang dikumpulkan:

 /* manifest.json */ { "app.js": "/fallback/js/app.4d03e1af64f68111703e.js", /* ... */ } 

Membuat index.html dengan tautan ke semua file javascript


Satu-satunya yang tersisa adalah menambahkan index.html dan menyisipkan path ke file yang diperlukan ke dalamnya.

Untuk menghasilkan file html, saya akan menggunakan html-webpack-plugin selama Modern Build. Plugin html-webpack-akan menyisipkan path ke file modern itu sendiri, dan saya akan mendapatkan path ke file fallback dari file yang dibuat pada langkah sebelumnya dan menempelkannya ke dalam HTML menggunakan plugin webpack kecil:

 /* webpack.prod.config.js */ const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModernBuildPlugin = require('path/to/ModernBuildPlugin'); module.exports = { mode: 'production', plugins: [ ...(isModern ? [ //  html-  Modern Build new HtmlWebpackPlugin({ filename: 'index.html', }), new ModernBuildPlugin(), ] : []), /* ... */ ], /* ... */ } 

 /* ModernBuildPlugin.js */ // Safari 10.1    nomodule. //      Safari   . //    : // https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc const safariFix = '!function(){var e=document,t=e.createE/* ...   ... */'; class ModernBuildPlugin { apply(compiler) { const pluginName = 'modern-build-plugin'; //    Fallback Build const fallbackManifest = require('path/to/dist/fallback/manifest.json'); compiler.hooks.compilation.tap(pluginName, (compilation) => { //    html-webpack-plugin, //      HTML compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(pluginName, (data, cb) => { //  type="module"  modern- data.body.forEach((tag) => { if (tag.tagName === 'script' && tag.attributes) { tag.attributes.type = 'module'; } }); //    Safari data.body.push({ tagName: 'script', closeTag: true, innerHTML: safariFix, }); //  fallback-   nomodule const legacyAsset = { tagName: 'script', closeTag: true, attributes: { src: fallbackManifest['app.js'], nomodule: true, defer: true, }, }; data.body.push(legacyAsset); cb(); }); }); } } module.exports = ModernBuildPlugin; 

Perbarui package.json:

 /* package.json */ { "scripts": { /* ... */ "build:full": "npm run build:fallback && npm run build:modern" }, /* ... */ } 

Menggunakan npm run build: perintah penuh, kita akan membuat satu file html dengan Modern dan Fallback Build. Setiap browser sekarang akan menerima JavaScript yang dapat dieksekusi.

Tambahkan Build Modern ke Aplikasi Anda


Untuk menguji solusi saya pada sesuatu yang nyata, saya membawanya ke salah satu proyek saya. Menyiapkan konfigurasi membutuhkan waktu kurang dari satu jam, dan ukuran file JavaScript menurun hingga 11%. Hasil hebat dengan implementasi sederhana.

Terima kasih telah membaca artikel sampai akhir!

Bahan yang digunakan


Source: https://habr.com/ru/post/id430950/


All Articles