Github リポジトリ
このセクションでは、Webpack について一般的に理解していることを前提としています。ここでは、作成者が運用環境で使用する Webpack コンパイル スクリプトの概要を示し、次に、計画を構成する前に、構成ファイルの設計目標、またはフロントエンド コンパイル環境が達成すべきと思われる特性の概要を説明したいと思います。これにより、将来 Webpack が廃止されたとしても、JSPM などの他のツールが利用できるようになります。同様の作業を完了するために使用できます。
単一の構成ファイル: 多くのプロジェクトでは、開発環境と実稼働環境用に 2 つの構成ファイルが作成されます。おそらく作者は怠け者で、これを行うのが好きではないため、作者の最初の機能は 1 つです。次に、さまざまなコンパイル コマンドをカプセル化し、npm を介して環境変数を渡し、構成ファイル内のさまざまな環境変数に従って動的に応答します。さらに、ボイラープレートを最小限の変更で他のプロジェクトに適用できることを確認してください。
複数のアプリケーションエントリのサポート: 単一ページのアプリケーションであっても、複数ページのアプリケーションであっても、HTML ファイルは Webpack のエントリとしてよく使用されます。作成者がプロジェクトを開発しているとき、多くの場合、複数のエントリ、つまり複数の HTML ファイルに直面する必要があり、この HTML ファイルはさまざまな JS または CSS ファイルをロードします。たとえば、ログイン ページとメイン インターフェイスは、多くの場合、2 つの異なる入り口と見なすことができます。 Webpack がネイティブに提唱する構成スキームはプロセス指向であり、ここでの著者はアプリケーション指向のカプセル化構成です。
デバッグ中のホットリロード: この機能は言うまでもありませんが、ホットリロードは中間サーバーであり、同時に 1 つのプロジェクトの監視しかサポートできないため、マルチアプリケーションの場合はパラメーターを追加する必要があります。構成、つまり、現在のデバッグ アプリケーションを指定します。
自動ポリフィル: これは Webpack に付属する機能ですが、作者は主に ES6、React、CSS (Flexbox) などの自動ポリフィルを実現するためにこれを統合しました。
リソースファイルの自動管理: この部分は主にテンプレートから対象のHTMLファイルを自動生成したり、画像/フォントなどのリソースファイルを自動処理したり、CSSファイルを自動抽出したりすることを指します。
ファイルのセグメント化と非同期読み込み: 複数のアプリケーションの共通ファイル。たとえば、それらがすべて React クラス ライブラリを参照している場合、これらのファイルを抽出できるため、フロントエンドは一定量のデータ送信を削減できます。さらに、コンポーネントの非同期読み込みをサポートする必要があります。たとえば、React Router を使用する場合は、必要に応じてコンポーネントの読み込みをサポートする必要があります。
リリース バージョンでは、たとえば、NODE_ENV 環境変数が運用環境と等しい場合にのみ、構成ファイルに追加する必要がある論理構成が存在します。 Webpack 構成ファイルで使用できます。以下に定義されています:
var webpack = require('webpack'); var production = process.env.NODE_ENV === 'production'; var plugins = [ new webpack.optimize.CommonsChunkPlugin({ name: 'main', // Move dependencies to our main file children: true, // Look for common dependencies in all children, minChunks: 2, // How many times a dependency must come up before being extracted }),]; if (production) { plugins = plugins.concat([ // Production plugins go here ]); }module.exports = { entry: './src', output: { path: 'builds', filename: 'bundle.js', publicPath: 'builds/', }, plugins: plugins, // ...};
リリース バージョンでは、次のような Webpack の一部の構成をオフにすることができます:
module.exports = { debug: !production, devtool: production ? false : 'eval',
{ "name": "webpack-boilerplate", "version": "1.0.0", "description": "Page-Driven Webpack Boilerplate For React-Redux Work Flow", "scripts": { "start": "node devServer.js?7.1.29", "storybook": "start-storybook -p 9001", "build:webpack": "NODE_ENV=production webpack -p --config webpack.config.js?7.1.29", "build": "npm run clean && npm run build:webpack", "build:style-check": "NODE_ENV=production CHECK=true webpack -p --config webpack.config.js?7.1.29", "deploy": "npm run build && ./node_modules/.bin/http-server dist", "clean": "rimraf dist", "lint": "eslint src" }, "repository": { "type": "git", "url": "https://github.com/wxyyxc1992/Webpack-React-Redux-Boilerplate" }, "keywords": [ "boilerplate", "live", "hot", "reload", "react", "reactjs", "hmr", "edit", "webpack", "babel", "react-transform", "PostCSS(FlexBox Polyfill)" ], "author": "Chevalier (http://github.com/wxyyxc1992)", "license": "MIT", "bugs": { "url": "https://github.com/wxyyxc1992/Webpack-React-Redux-Boilerplate/issues" }, "homepage": "https://github.com/wxyyxc1992/Webpack-React-Redux-Boilerplate", "devDependencies": { "@kadira/storybook": "^1.17.1", ... }, "dependencies": { "boron": "^0.1.2", ... } }
var path = require('path'); var webpack = require('webpack'); //PostCSS pluginsvar autoprefixer = require('autoprefixer'); //webpack pluginsvar ProvidePlugin = require('webpack/lib/ProvidePlugin'); var DefinePlugin = require('webpack/lib/DefinePlugin'); var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var CopyWebpackPlugin = require('copy-webpack-plugin'); var WebpackMd5Hash = require('webpack-md5-hash'); var ExtractTextPlugin = require("extract-text-webpack-plugin"); var NODE_ENV = process.env.NODE_ENV || "develop";//获取命令行变量 //@region 可配置区域//定义统一的Application,不同的单页面会作为不同的Application /** * @function 开发状态下默认会把JS文本编译为main.bundle.js,然后使用根目录下dev.html作为调试文件. * @type {*[]} */ var apps = [ { //required id: "index", //编号 title: "Index",//HTML文件标题 entry: { name: "index",//该应用的入口名 src: "./src/index.js?7.1.29",//该应用对应的入口文件 },//入口文件 indexPage: "./src/index.html",//主页文件 //optional dev: false,//判断是否当前正在调试,默认为false compiled: true//判斷當前是否加入编译,默认为true }, { id: "helloworld", title: "HelloWorld", entry: { name: "helloworld", src: "./src/modules/helloworld/container/app.js?7.1.29" }, indexPage: "./src/modules/helloworld/container/helloworld.html", dev: false, compiled: true }, { id: "todolist", title: "TodoList", compiled: false }, { //required id: "counter",//编号 title: "Counter",//HTML文件标题 entry: { name: "counter",//该应用的入口名 src: "./src/modules/counter/container/app.js?7.1.29",//该应用对应的入口文件 },//入口文件 indexPage: "./src/modules/counter/container/counter.html",//主页文件 //optional dev: false,//判断是否当前正在调试,默认为false compiled: true//判斷當前是否加入编译,默认为true }, { //required id: "form",//编号 title: "Form",//HTML文件标题 entry: { name: "form",//该应用的入口名 src: "./src/modules/form/form.js?7.1.29"//该应用对应的入口文件 },//入口文件 indexPage: "./src/modules/form/form.html",//主页文件 //optional dev: true,//判断是否当前正在调试,默认为false compiled: true//判斷當前是否加入编译,默认为true }];//定义非直接引用依赖//定义第三方直接用Script引入而不需要打包的类库//使用方式即为 var $ = require("jquery")const externals = { jquery: "jQuery", pageResponse: 'pageResponse'}; /*********************************************************/ /*********************************************************/ /*下面属于静态配置部分,修改请谨慎*/ /*********************************************************/ /*********************************************************/ //开发时的入口考虑到热加载,只用数组形式,即每次只会加载一个文件 var devEntry = [ 'eventsource-polyfill', 'webpack-hot-middleware/client',]; //生产环境下考虑到方便编译成不同的文件名,所以使用数组 var proEntry = { "vendors": "./src/vendors.js?7.1.29",//存放所有的公共文件}; //定义HTML文件入口,默认的调试文件为src/index.htmlvar htmlPages = []; //遍历定义好的app进行构造 apps.forEach(function (app) { //判断是否加入编译 if (app.compiled === false) { //如果还未开发好,就设置为false return; } //添加入入口 proEntry[app.entry.name] = app.entry.src; //构造HTML页面 htmlPages.push({ filename: app.id + ".html", title: app.title, // favicon: path.join(__dirname, 'assets/images/favicon.ico'), template: 'underscore-template-loader!' + app.indexPage, //默认使用underscore inject: false, // 使用自动插入JS脚本, chunks: ["vendors", app.entry.name] //选定需要插入的chunk名 }); //判断是否为当前正在调试的 if (app.dev === true) { //如果是当前正在调试的,则加入到devEntry devEntry.push(app.entry.src); }});//@endregion 可配置区域//基本配置 var config = { devtool: 'source-map', //所有的出口文件,注意,所有的包括图片等本机被放置到了dist目录下,其他文件放置到static目录下 output: { path: path.join(__dirname, 'dist'),//生成目录 filename: '[name].bundle.js',//文件名 sourceMapFilename: '[name].bundle.map'//映射名 // chunkFilename: '[id].[chunkhash].chunk.js',//块文件索引 }, //配置插件 plugins: [ // new WebpackMd5Hash(),//计算Hash插件 new webpack.optimize.OccurenceOrderPlugin(), new webpack.DefinePlugin({ 'process.env': { //因为使用热加载,所以在开发状态下可能传入的环境变量为空 'NODE_ENV': process.env.NODE_ENV === undefined ? JSON.stringify('develop') : JSON.stringify(NODE_ENV) }, //判断当前是否处于开发状态 __DEV__: process.env.NODE_ENV === undefined || process.env.NODE_ENV === "develop" ? JSON.stringify(true) : JSON.stringify(false) }), //提供者fetch Polyfill插件 new webpack.ProvidePlugin({ // 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch' }), //提取出所有的CSS代码 new ExtractTextPlugin('[name].css'), //自动分割Vendor代码 new CommonsChunkPlugin({name: 'vendors', filename: 'vendors.bundle.js', minChunks: Infinity}), //自动分割Chunk代码 new CommonsChunkPlugin({ children: true, async: true, }) ], module: { loaders: [ { test: /\.(js|jsx)$/, exclude: /(libs|node_modules)/, loader:"babel", query: { presets: ["es2015", "react", "stage-2"], plugins: [ ["typecheck"], ["transform-flow-strip-types"], ["syntax-flow"], ["transform-class-properties"], ["transform-object-rest-spread"] ] } }, { test: /\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)(\?\S*)?$/, loader: 'url-loader?limit=8192&name=assets/imgs/[hash].[ext]' },// inline base64 URLs for <=8k images, direct URLs for the rest { test: /\.vue$/, loader: 'vue' } ] }, postcss: [ autoprefixer({browsers: ['last 10 versions', "> 1%"]}) ],//使用postcss作为默认的CSS编译器 resolve: { alias: { libs: path.resolve(__dirname, 'libs'), nm: path.resolve(__dirname, "node_modules"), assets: path.resolve(__dirname, "assets"), } }};//进行脚本组装config.externals = externals;//自动创建HTML代码htmlPages.forEach(function (p) { config.plugins.push(new HtmlWebpackPlugin(p));});//为开发状态下添加插件if (process.env.NODE_ENV === undefined || process.env.NODE_ENV === "develop") { //配置SourceMap config.devtool = 'cheap-module-eval-source-map'; //设置入口为调试入口 config.entry = devEntry; //設置公共目錄名 config.output.publicPath = '/dist/'//公共目录名 //调试状态下的CSS config.module.loaders.push({ test: /\.(scss|sass|css)$/, loader: 'style-loader!css-loader!postcss-loader!sass' }); //添加插件 config.plugins.push(new webpack.HotModuleReplacementPlugin()); config.plugins.push(new webpack.NoErrorsPlugin());} else { //如果是生产环境下 config.entry = proEntry; //如果是生成环境下,将文件名加上hash config.output.filename = '[name].bundle.js.[hash:8]'; //設置公共目錄名 config.output.publicPath = '/'//公共目录名 //发布状态下添加Loader config.module.loaders.push({ test: /\.(scss|sass|css)$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader!postcss-loader!sass') }); //添加代码压缩插件 config.plugins.push( new webpack.optimize.UglifyJsPlugin({ compressor: { warnings: false } })); //添加MD5计算插件 //判断是否需要进行检查 if (process.env.CHECK === "true") { config.module.loaders[0].loaders.push("eslint-loader"); }}module.exports = config;
var path = require('path'); var express = require('express'); var webpack = require('webpack');//默认是开发时配置 var config = require('./webpack.config'); var app = express(); var compiler = webpack(config); app.use(require('webpack-dev-middleware')(compiler, { noInfo: true, publicPath: config.output.publicPath})); app.use(require('webpack-hot-middleware')(compiler)); app.get('*', function(req, res) { res.sendFile(path.join(__dirname + "/src/", "dev.html"));});//监听本地端口 app.listen(3000, 'localhost', function(err) { if (err) { console.log(err); return; } console.log('Listening at http://localhost:3000');});
このセクションを開始する前に、マスターによる記事「大企業でフロントエンド コードを開発およびデプロイする方法」を読むことができます。
静的ファイルの場合、最初の取得後にファイルの内容が変更されていない場合、ブラウザはキャッシュされたファイルを直接読み取ることができます。では、キャッシュ設定が長すぎてファイルを更新する必要がある場合はどうすればよいでしょうか?ファイル内容の MD5 をファイル名として使用するのは良い解決策です。 webpackを使って実装する方法を見てみましょう
output: { path: xxx, publicPath: yyy, filename: '[name]-[chunkhash:6].js'}
パッケージ化されたファイル名にはハッシュ値が付加されています
const bundler = webpack(config)bundler.run((err, stats) => { let assets = stats.toJson().assets let name for (let i = 0; i < assets.length; i++) { if (assets[i].name.startsWith('main')) { name = assets[i].name break } } fs.stat(config.buildTemplatePath, (err, stats) => { if (err) { fs.mkdirSync(config.buildTemplatePath) } writeTemplate(name) }) })
webpackのAPIを手動で呼び出してパッケージ化されたファイル名を取得し、writeTemplateでHTMLコードを更新します。 gitst にある完全なコード。このようにして、更新を気にせずにファイル キャッシュを長期間設定できます。