blog

初心者から上級者までのwebpack (2)

デフォルトでは、webpackはすべてのモジュールを単一のファイル、例えばmain.jsにパッケージ化します。 時には、jQueryやlodashのような変更の少ないサードパーティモジュールがあります...

Sep 16, 2020 · 28 min. read
シェア

Webpack

ディレクトリ構造全体は

const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); module.exports = { mode: 'development', devtool: 'cheap-module-eval-source-map', // mode: 'production', // devtool: 'cheap-module-source-map', entry: { main: './src/index.js' }, devServer: { contentBase: './dist', open: true, port: 8080, hot: true, hotOnly: true }, module: { rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', }, { test: /\.(jpg|png|gif)$/, use: { loader: 'url-loader', options: { name: '[name]_[hash].[ext]', outputPath: 'images/', limit: 10240 } } }, { test: /\.(eot|ttf|svg)$/, use: { loader: 'file-loader' } }, { test: /\.scss$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2 } }, 'sass-loader', 'postcss-loader' ] }, { test: /\.css$/, use: [ 'style-loader', 'css-loader', 'postcss-loader' ] }] }, plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), new CleanWebpackPlugin(['dist']), new webpack.HotModuleReplacementPlugin() ], optimization: { usedExports: true }, output: { filename: '[name].js', path: path.resolve(__dirname, 'dist') } }

-1 Tree Shaking

math.jsを記述するときに、2つのメソッドを追加し、マイナスをエクスポートしますが、index.jsのみメソッドを追加インポートすると、自動的にコードのマイナスを削除するには、パッケージ化されたファイルがインポートされていないように、ツリーを振ることができますが、開発と生産の2つのモードでは、開発では、わずかな違いがあります。optimization: {usedExports: true}開発モードでは、追加の設定する必要があり、package.jsonの設定 "sideEffects "で:falseは、すべてのツリーを振ることを示し

import '@/babel/polyfill' import '."sideEffects": ['@/babel/polyfill','*.css']/style.css'はそれ自体インポートされないので、Tree Shakingはそれらを完全に無視します。

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	// mode: 'production',
	// devtool: 'cheap-module-source-map',
	...
	
	module: {
		...
	},
	plugins: [
		...
	],
	optimization: {
		usedExports: true
	},
	...
}
// math.js export const add = (a, b) => { console.log( a + b ); } export const minus = (a, b) => { console.log( a - b ); }
// index.js
// Tree Shaking   ES Module
// ES Module下は静的インポート
// CommonJs 下はダイナミックにインポートする
import { add } from './math.js';
add(1, 2);

npm run bundle から得られるバンドルファイルには

export provided: add minus
export used: add

開発モードでは、プロンプトが表示されるだけですが、それでもすべてのコードをパッケージ化します。インポートされていないコードを自動的に削除すると、ソースファイルとパッケージ化されたファイルのsourceMapが一致しなくなる可能性があり、デバッグに不便だからです。

プロダクションモードでは、実際にツリーシェイキングを行うためにsideEffectsを設定するだけです!

-2 development and production

optimization: {usedExports: true}通常、開発環境と生成環境のwebpackの設定は同じではありません。例えば、Tree Shakingの場合、開発環境は設定のみで、devserverも同じですが、devtoolは2つとも同じではありません!

そうすると、パッケージするたびにwebpack.config,jsファイルを手動で変更する必要があります。

// package.json
{
	"scripts": {
 "dev": "webpack-dev-server --config ./webpack.dev.js",
 "build": "webpack --config ./webpack.prod.js"
 },
}

また、webpack.dev.jsとwebpack.prod.jsの公開設定をwebpack.common.jsに展開し、新しいビルドフォルダを作成して、buildの下に3つのファイルを配置することで最適化できます。

webpack.common.js

const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports = { entry: { main: './src/index.js' }, module: { rules: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', }, { test: /\.(jpg|png|gif)$/, use: { loader: 'url-loader', options: { name: '[name]_[hash].[ext]', outputPath: 'images/', limit: 10240 } } }, { test: /\.(eot|ttf|svg)$/, use: { loader: 'file-loader' } }, { test: /\.scss$/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2 } }, 'sass-loader', 'postcss-loader' ] }, { test: /\.css$/, use: [ 'style-loader', 'css-loader', 'postcss-loader' ] }] }, plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html' }), new CleanWebpackPlugin(['dist'],{ { root: path.resolve(__dirname, '../') //アドレスをbuildと同じレベルに変更する } }) ], output: { filename: '[name].js', // path: path.resolve(__dirname, '../', 'dist') //同じレベルをビルドするためにパッケージファイルのアドレスを生成する path: path.resolve(__dirname, '../dist') //同じレベルをビルドするためにパッケージファイルのアドレスを生成する } }

webpack.dev.js

const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const devConfig = {
	mode: 'development',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		open: true,
		port: 8080,
		hot: true
	},
	plugins: [
		new webpack.HotModuleReplacementPlugin()
	],
	optimization: {
		usedExports: true
	}
}
module.exports = merge(commonConfig, devConfig);

webpack.prod.js

const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const prodConfig = {
	mode: 'production',
	devtool: 'cheap-module-source-map'
}
module.exports = merge(commonConfig, prodConfig);
// package.json
{
 "scripts": {
 "dev": "webpack-dev-server --config ./build/webpack.dev.js",
 "build": "webpack --config ./build/webpack.prod.js"
 },
}

開発環境のための npm run dev

npm run build for production

-3 Code Splitting

デフォルトでは、webpackはすべてのモジュールを1つのファイル、例えばmain.jsにパッケージします。

例えばjQueryやlodashなどです。これらのモジュールは別のファイルにパッケージできます。

  • 1.手作業による分割
// lodash.js import _ from 'lodash' window._ = _
// index.js
console.log(_.join(['a','b','c'],'---'))

そして、前述のようにパッケージングのために複数のエントリを設定し、HtmlWebpackPluginを介してhtmlに2つのファイルを導入することができます。

module.exports = {
 entry: {
 	lodash: './src/lodash.js',
 	main: './src/index.js'
 },
 module: {
 	rules: [{ 
 		test: /\.js$/, 
 		exclude: /node_modules/, 
 		loader: 'babel-loader',
 	}]
 },
 plugins: [
 	new HtmlWebpackPlugin({
 		template: 'src/index.html'
 	}), 
 	new CleanWebpackPlugin(['dist'],{
 	{
 root: path.resolve(__dirname, '../') //アドレスをbuildと同じレベルに変更する
 }
 })
 ],
 output: {
 	filename: '[name].js',
 	path: path.resolve(__dirname, '../dist') //同じレベルをビルドするためにパッケージファイルのアドレスを生成する
 }
}
  • import _ from 'lodash' 同期モジュール

最適化を設定するだけ

// index.js import _ from 'lodash' console.log(_.join(['a','b','c'],'---'))
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
	entry: {
		main: './src/index.js'
	},
	module: {
		rules: [{ 
			test: /\.js$/, 
			exclude: /node_modules/, 
			loader: 'babel-loader',
		}]
	},
	plugins: [
		new HtmlWebpackPlugin({
			template: 'src/index.html'
		}), 
		new CleanWebpackPlugin(['dist'], {
			root: path.resolve(__dirname, '../')
		})
	],
	optimization: {
		splitChunks: {
			chunks: 'all'
		}
	},
	output: {
		filename: '[name].js',
		path: path.resolve(__dirname, '../dist')
	}
}

main.jsとvendor~main.jsをパッケージ化し、htmlに導入します。

  • 3.非同期モジュール
// index.js function getComponent() { return import('lodash').then(({ default: _ }) => { var element = document.createElement('div'); element.innerHTML = _.join(['a', 'b', 'c'], '---'); return element; }) } getComponent().then(element => { document.body.appendChild(element); });

何も設定しなくても、コードは自動的に分割され、新しいファイルmain.jsと0.jsに配置されます。

これはマジックコメントと呼ばれ、生成されるモジュールのファイル名を指定することができます。/* webpackChunkName: "jquery" */マジックコメントを使うには

// .babelrc
{
	presets: [
		[
			"@babel/preset-env", {
				targets: {
					chrome: "67",
				},
				useBuiltIns: 'usage'
			}
		],
		"@babel/preset-react"
	],
	plugins: ["@babel/plugin-syntax-dynamic-import"]
}

-4 Lazy Loading

レイジーローディングはwebpackのコンセプトではなく、ESのコンセプトです。

これは、すべてのコードを一度にロードするタイプです。

// index.js import _ from 'lodash' document.addEventListener('click', () => { var element = document.createElement('div') element.innerHTML = _.join(['hello', 'world'], '-') document.body.appendChild(element) })

以下の非同期コードは、インタフェースをクリックすると、必要なモジュールをロードするために行くでしょう、遅延ロード動作を達成するために書くことができる効果は、ページでは、main.jsの先頭のみがロードされ、その後、ページをクリックすると、loadsh関数にロードされ、文字列のスプライシング処理を実現するために、この関数のメソッドの一部を呼び出すと、最終的にページ上にレンダリングされます。

function getComponent() { return import(/* webpackChunkName: "lodash" */ 'lodash').then( ({ default: _ }) => { var element = document.createElement('div') element.innerHTML = _.join(['hello', 'world'], '-') return element } ) } document.addEventListener('click', () => { getComponent().then(element => { document.body.appendChild(element) }) })

ES7のasyncとawaitを使えば、上記のコードは次のように書き直せます。

async function getComponent() { const { default: _ } = await import(/* webpackChunkName: "lodash" */ 'lodash') const element = document.createElement('div') element.innerHTML = _.join(['hello', 'world'], '-') return element } document.addEventListener('click', () => { getComponent().then(element => { document.body.appendChild(element) }) })

-5 パッキング分析

まず、package.jsonを変更します。

// package.json
"scripts": {
 "dev": "webpack --profile --json > stats.json --config webpack.dev.js"
}

npm run devはstats.jsonを生成します。

webpack解析ツールのgitリポジトリのアドレス

その他の分析ツールのアドレス

-6 preloading prefetching

前面に書き込むと、クロームブラウザは、コードカバレッジを表示する機能があります、つまり、F12をctrl + shift + pの後、コードの使用率を表示するには、カバレッジを入力してください

元のコードから見てみましょう。

// index.js document.addEventListener('click', () => { var element = document.createElement('div') element.innerHTML = 'nice day!' document.body.appendChild(element) })

クリック関数のコードはパッケージング後に使用されないので、修正してください。

// index.js document.addEventListener('click', () =>{ import('./click.js').then(({default: func}) => { func(); }) });
// click.js
function handleClick() {
	const element = document.createElement('div');
	element.innerHTML = 'Dell Lee';
	document.body.appendChild(element);
}
export default handleClick;

この修正により、コードがより効率的になり、最初の画面ロードの時間が短縮されます。

そのため、webpackがコードをパッケージに分割するように設定されている場合、チャンクのデフォルトは、すべてまたは初期ではなく、非同期です。webpackは、同期コードはキャッシュを増やすことができるだけでなく、そのようなコンポーネントの非同期ロードのみが本当にWebページパッケージのパフォーマンスを向上させることができると考えているため、最初の時間はまだ多くのリソースをロードする必要があり、実際のパフォーマンスの向上は非常に限られています

このビューでは、または非同期ロードできるようにするには、モジュールのコードをロードすることを望むが、非常に遅いロードを恐れています。たとえば、サイトのホームページをロードするときに、非同期にログインモーダルボックスモジュールをロードすることができますが、私はログインをクリックするのが怖い、モーダルボックスモジュールは非常に遅いので、非同期コードプリロードとメインビジネス文書を一緒にロードするプリロードネットワークアイドルがあります。

/* webpackPrefetch: true */
/* webpackPreload: true */
import(/* webpackPrefetch: true */'./click.js')

これは、帯域幅が解放された後、黙ってログインモーダルボックスモジュールのコードのロードのために、両方のホームページのロード高速のニーズを満たすために、ウェブサイトのホームページのロードが完了しているだけでなく、ログインのロード高速のニーズを満たすためにすることができます!

Read next

リボンの負荷分散 -> ソースコード解析

リクエストがサーバーに到達した後、これらのロードバランサは、特定のアルゴリズムに従って宛先サーバーにリクエストをルーティングするなど、Nginx、F5などのサーバー側の負荷分散、。 クライアント側のロードバランシングは、リボンとは対照的に、サービス消費者であるクライアントがサーバーアドレスのリストを持っており、呼び出し元がリクエストを行う前にロードバランシングアルゴリズムを使用してアクセスするサーバーを選択し、ロードバランシングアルゴリズム ...

Sep 16, 2020 · 6 min read