درس دهم: تفاوت‌های Production و Development

Differences between Production and Development

23 شهریور 1399
درسنامه درس 10 از سری آموزش Webpack
02-Webpack-4-The-Complete-Tutorial-For-Beginners-lesson10

اگر یادتان باشد در جلسات اولیه این دوره آموزشی گزینه ای به نام mode در فایل webpack.config.js وجود داشت و من آن را روی none قرار دادم:

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.[contenthash].js',
        path: path.resolve(__dirname, './dist'),
        publicPath: ''
    },
    mode: 'none',
// بقیه کد ها //

این گزینه از نسخه چهارم webpack اضافه شده است و کار ما را بسیار ساده تر کرده است. mode یعنی حالت کاری webpack که می تواند یکی از دو نوع زیر باشد:

  • توسعه یا development: این گزینه مخصوص زمانی است که در حال برنامه نویسی هستیم و هدف خاصی برای بهینه سازی و کوچک کردن حجم فایل ها نداریم. در این حالت به دنبال نمایش گزینه های بیشتر برای خطایابی و راحت کردن کار برنامه نویسی هستیم (راحت بودن برنامه نویس).
  • تولید یا production: این گزینه مخصوص زمانی است که وب سایت ما روی سرور آپلود می شود تا تمام افراد از آن استفاده کنند. هدف اصلی ما در این حالت بهینه سازی حداکثری و کاهش حجم تمام فایل ها است (راحت بودن کاربر).

من با production شروع می کنم. این حالت پلاگین های بسیاری را فعال می کند (به طور مثال Terser) شما می توانید لیست کاملی از این پلاگین ها را در قسمت mode از وب سایت رسمی webpack پیدا کنید. این وب سایت حالت production را به شکل زیر تعریف می کند:

// webpack.production.config.js
module.exports = {
+  mode: 'production',
- performance: {
-   hints: 'warning'
- },
- output: {
-   pathinfo: false
- },
- optimization: {
-   namedModules: false,
-   namedChunks: false,
-   nodeEnv: 'production',
-   flagIncludedChunks: true,
-   occurrenceOrder: true,
-   sideEffects: true,
-   usedExports: true,
-   concatenateModules: true,
-   splitChunks: {
-     hidePathInfo: true,
-     minSize: 30000,
-     maxAsyncRequests: 5,
-     maxInitialRequests: 3,
-   },
-   noEmitOnErrors: true,
-   checkWasmTypes: true,
-   minimize: true,
- },
- plugins: [
-   new TerserPlugin(/* ... */),
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }),
-   new webpack.optimize.ModuleConcatenationPlugin(),
-   new webpack.NoEmitOnErrorsPlugin()
- ]
}

تمام این ها خصوصیات و پلاگین هایی هستند که در حالت production فعال می شوند. البته نیازی نیست همه آن ها را یاد بگیریم. من فعلا کد خود را روی حالت production می گذارم. برای این کار به webpack.config.js بروید و مثل من عمل کنید:

    mode: 'production',

یکی از کارهایی که حالت production می کند، فعالسازی ثابت خاصی به نام process.env.NODE_ENV و تعیین مقدار production برای آن است. بدین صورت می توانیم از برنامه خود به این مقدار دسترسی داشته باشیم و بفهمیم در حالت production یا development هستیم. برای تست این موضوع می توانید به index.js رفته و کد زیر را به آن اضافه کنید:

if (process.env.NODE_ENV === 'production') {
    console.log('Production mode');
} else if (process.env.NODE_ENV === 'development') {
    console.log('Development mode');
}

این کد ثابت process.env.NODE_ENV را بررسی کرده و اگر در حالت development بودیم عبارت development mode را در کنسول چاپ می کنیم، برعکس آن برای حالت production نیز صحیح است. یکی از تفاوت های بین حالت production و development نحوه بررسی و گزارش خطا ها است. برای تست این موضوع، متدی را صدا می زنم که وجود نداشته باشد تا به خطا برخورد کنیم:

if (process.env.NODE_ENV === 'production') {
    console.log('Production mode');
} else if (process.env.NODE_ENV === 'development') {
    console.log('Development mode');
}

helloWorldButton.SomeMethod();

حالا دستور npm run build را اجرا کنید. سپس به مرورگر رفته و قسمت کنسول را باز کنید:

خطا ها در حالت production
خطا ها در حالت production

می بینید که علاوه بر چاپ عبارت production mode، خطای ما نیز برایمان نمایش داده شده است اما از آنجایی که تمام کدهای ما درون یک فایل bundle جمع شده است، فقط می بینیم که خط اول از این فایل دارای خطا است. مشکل اینجاست که کد ما در حالت production کوچک و minify می شود بنابراین تمام کد های ما در خط اول خواهد بود! متوجه مشکل شدید؟ امکان ندارد بفهمیم که خطا از کدام قسمت بوده است.

حالا به webpack.config.js برگشته و حالت برنامه را روی Development بگذارید:

    mode: 'development',

سپس دوباره npm run build را اجرا کرده و به مرورگر برگردید:

خطا ها در حالت development
خطا ها در حالت development

همانطور که می بینید حالا مشخص شده است که خطای ما از خط 16 بوده است. این یکی از تفاوت های حالت production با development است. شما می توانید در وب سایت رسمی Webpack در این باره بیشتر مطالعه کنید.

فایل پیکربندی برای production و development

در حال حاضر مشکل کوچکی داریم. هر بار که بخواهیم از برنامه در حالت development استفاده کنیم باید به فایل پیکربندی رفته و از آنجا mode را روی development قرار بدهیم، و برای رفتن به حالت production نیز باید همین پروسه را دوباره اجرا کنیم. این فرآیند کمی خسته کننده و وقت گیر است به همین دلیل webpack راه حلی برای این مشکل ارائه داده است. ما می توانیم دو فایل پیکربندی مختلف بسازیم که یکی برای production و دیگری برای Development باشند. بر این اساس فایل webpack.config.js را به webpack.production.config.js تغییر نام دهید. یک فایل دیگر به نام webpack.dev.config.js را نیز در کنار این فایل ایجاد کنید. سپس همه کد های فایل webpack.production.config.js را کپی کرده و درون این فایل قرار دهید.

من از فایل webpack.production.config.js شروع می کنم. اولین کار این است که mode را روی production بگذاریم. سپس از قسمت plugins کد مربوط به new TerserPlugin را حذف کنید چرا که در حالت production این پلاگین به صورت پیش فرض فعال می باشد (طبیعتا دستور require آن را نیز از ابتدای فایل حذف کنید). بقیه موارد این فایل مناسب حالت production هستند و به آن ها دست نمی زنیم، بنابراین کد نهایی این فایل به شکل زیر است:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html=webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.[contenthash].js',
        path: path.resolve(__dirname, './dist'),
        publicPath: ''
    },
    mode: 'production',
    module: {
        rules: [
            {
                test: /\.(png|jpg)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name][contenthash:5].[ext]'
                        }
                    }
                ]
            },
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader, 'css-loader'
                ]
            },
            {
                test: /\.scss$/,
                use: [
                    MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'
                ]
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/env'],
                        plugins: ['transform-class-properties']
                    }
                }
            },
            {
                test: /\.hbs$/,
                use: {
                    'handlebars-loader'
                }
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'styles.[contenthash].css'
        }),
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: 'Hello World',
            description: 'your description here',
            template: 'src/index.hbs'
        })
    ]
}

حالا نوبت webpack.dev.config.js است. ابتدا mode را روی development بگذارید. از طرفی می توانید contenthash را از قسمت filename (در output) حذف کنید چرا که در حالت توسعه نیازی به نگرانی برای browser caching نیست مگر آنکه در هنگام کدنویسی مرورگر شما مکررا کدهایتان را کش می کند. در چنین حالتی می توانید با ctrl + f5 از شر cache خلاص شوید اما اگر دوست دارید contenthash باقی بماند نیز مشکلی نیست. در مرحله بعد Terser را مانند فایل قبلی حذف کنید چرا که در حالت توسعه نیازی به minify کردن کد ها نیست، بلکه باعث سخت تر شدن debugging نیز می شود. همچنین در حالت توسعه نیازی نداریم که کدهای CSS را درون یک فایل واحد ادغام کنیم بنابراین MiniCssExtractPlugin را نیز حذف می کنم (هم از قسمت plugins و هم دستور require مربوط به آن). حالا که MiniCssExtractPlugin را حذف کرده ایم به قسمت rules مربوط به css و scss بروید و به جای آن از style-loader استفاده کنید. بنابراین محتویات این فایل به شکل زیر در می آید:

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html=webpack-plugin');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, './dist'),
        publicPath: ''
    },
    mode: 'development',
    module: {
        rules: [
            {
                test: /\.(png|jpg)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name][contenthash:5].[ext]'
                        }
                    }
                ]
            },
            {
                test: /\.css$/,
                use: [
                    'style-loader', 'css-loader'
                ]
            },
            {
                test: /\.scss$/,
                use: [
                    'style-loader', 'css-loader', 'sass-loader'
                ]
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/env'],
                        plugins: ['transform-class-properties']
                    }
                }
            },
            {
                test: /\.hbs$/,
                use: {
                    'handlebars-loader'
                }
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: 'Hello World',
            description: 'your description here',
            template: 'src/index.hbs'
        })
    ]
}

با استفاده کمتر از پلاگین ها، فرآیند گرفتن خروجی از پروژه بسیار سریع تر می شود. البته در برنامه بسیار کوچک ما تغییر محسوسی را حس نخواهیم کرد اما در پروژه های بزرگ تر، تغییر واقعا قابل دیدن است.

از آنجایی که دو فایل پیکربندی جداگانه داریم باید دو دستور جداگانه برای Webpack تعریف کنیم تا هر بار بر اساس یکی از این فایل های پیکربندی کار کند. برای این کار به package.json بروید و مثل من عمل کنید:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.production.config.js",
    "dev": "webpack --config webpack.dev.config.js"
  },

در قسمت Scripts تعیین کرده ایم که فایل webpack.production.config.js مخصوص دستور build است بنابراین برای production از آن استفاده می کنیم (این کار با config-- انجام می شود). فایل webpack.dev.config.js را نیز برای حالت توسعه در نظر گرفته ایم. از این به بعد برای گرفتن خروجی در حالت production از دستور npm run build و برای گرفتن خروجی در حالت توسعه از دستور npm run dev استفاده خواهیم کرد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش Webpack توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.