Build bersyarat berdasarkan lingkungan menggunakan Webpack

95

Saya memiliki beberapa hal untuk pengembangan - mis. Ejekan yang saya ingin agar file build terdistribusi saya tidak membengkak.

Di RequireJS Anda dapat meneruskan konfigurasi dalam file plugin dan secara kondisional memerlukan hal-hal berdasarkan itu.

Untuk webpack sepertinya tidak ada cara untuk melakukan ini. Pertama-tama untuk membuat konfigurasi runtime untuk lingkungan, saya telah menggunakan resolusinya.alias untuk menunjuk kembali kebutuhan bergantung pada lingkungan, misalnya:

// All settings.
var all = {
    fish: 'salmon'
};

// `envsettings` is an alias resolved at build time.
module.exports = Object.assign(all, require('envsettings'));

Kemudian saat membuat konfigurasi webpack saya dapat secara dinamis menetapkan file mana yang envsettingsmenunjuk ke (yaitu webpackConfig.resolve.alias.envsettings = './' + env).

Namun saya ingin melakukan sesuatu seperti:

if (settings.mock) {
    // Short-circuit ajax calls.
    // Require in all the mock modules.
}

Tapi jelas saya tidak ingin membangun file tiruan itu jika lingkungan tidak tiruan.

Saya mungkin dapat secara manual menunjuk kembali semua yang diperlukan ke file rintisan menggunakan resolusikan.alias lagi - tetapi apakah ada cara yang terasa kurang hacky?

Ada ide bagaimana saya bisa melakukan itu? Terima kasih.

Dominic
sumber
Perhatikan bahwa untuk saat ini saya telah menggunakan alias untuk menunjuk ke file (rintisan) kosong di lingkungan yang tidak saya inginkan (mis. Require ('mocks') akan mengarah ke file kosong di envs non-tiruan. Sepertinya sedikit hacky tetapi itu bekerja.
Dominic

Jawaban:

60

Anda dapat menggunakan plugin definisikan .

Saya menggunakannya dengan melakukan sesuatu yang sederhana seperti ini di file build webpack Anda di mana envjalur ke file yang mengekspor objek pengaturan:

// Webpack build config
plugins: [
    new webpack.DefinePlugin({
        ENV: require(path.join(__dirname, './path-to-env-files/', env))
    })
]

// Settings file located at `path-to-env-files/dev.js`
module.exports = { debug: true };

dan kemudian ini di kode Anda

if (ENV.debug) {
    console.log('Yo!');
}

Ini akan menghapus kode ini dari file build Anda jika kondisinya salah. Anda dapat melihat contoh build Webpack yang berfungsi di sini .

Matt Derrick
sumber
Saya sedikit bingung dengan solusi ini. Tidak disebutkan bagaimana saya harus mengatur env. Melihat melalui contoh itu, sepertinya mereka menangani bendera itu melalui gulp dan yarg yang tidak digunakan semua orang.
Andre
1
Bagaimana cara kerjanya dengan linter? Apakah Anda harus secara manual menentukan variabel global baru yang ditambahkan di plugin Define?
tandai
2
@ tandai ya. Tambahkan sesuatu seperti "globals": { "ENV": true }ke .eslintrc Anda
Matt Derrick
bagaimana cara mengakses variabel ENV dalam sebuah komponen? Saya mencoba solusi di atas tetapi saya masih mendapatkan kesalahan bahwa ENV tidak ditentukan
jasan
20
Itu TIDAK menghapus kode dari file build! Saya mengujinya dan kodenya ada di sini.
Lionel
42

Tidak yakin mengapa jawaban "webpack.DefinePlugin" adalah yang teratas di mana pun untuk menentukan impor / kebutuhan berbasis Lingkungan.

The masalah dengan pendekatan itu adalah bahwa Anda masih memberikan semua modul yang ke klien -> cek dengan Webpack-bundle-analyezer misalnya. Dan tidak mengurangi ukuran bundle.js Anda sama sekali :)

Jadi yang benar-benar berfungsi dengan baik dan lebih logis adalah: NormalModuleReplacementPlugin

Jadi daripada melakukan persyaratan bersyarat on_client -> cukup tidak menyertakan file yang tidak diperlukan ke bundel di tempat pertama

Semoga membantu

Roman Zhyliov
sumber
Nice tidak tahu tentang plugin itu!
Dominic
Dengan skenario ini, tidakkah Anda akan memiliki beberapa build per lingkungan? Misalnya jika saya memiliki alamat layanan web untuk lingkungan dev / QA / UAT / produksi, saya akan memerlukan 4 wadah terpisah, 1 untuk setiap lingkungan. Idealnya Anda memiliki satu penampung dan meluncurkannya dengan variabel lingkungan untuk menentukan konfigurasi mana yang akan dimuat.
Brett Mathe
Tidak terlalu. Itulah yang Anda lakukan dengan plugin -> Anda menentukan lingkungan Anda melalui env vars dan itu hanya membangun satu wadah, tetapi untuk lingkungan tertentu tanpa inklusi yang berlebihan. Tentu saja itu juga tergantung pada bagaimana Anda mengatur konfigurasi webpack Anda dan jelas Anda dapat membangun semua build, tetapi itu bukan tentang dan apa plugin ini.
Roman Zhyliov
34

Gunakan ifdef-loader. Di file sumber Anda, Anda dapat melakukan hal-hal seperti

/// #if ENV === 'production'
console.log('production!');
/// #endif

webpackKonfigurasi yang relevan adalah

const preprocessor = {
  ENV: process.env.NODE_ENV || 'development',
};

const ifdef_query = require('querystring').encode({ json: JSON.stringify(preprocessor) });

const config = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: `ifdef-loader?${ifdef_query}`,
        },
      },
    ],
  },
  // ...
};
May Oakes
sumber
3
Saya memberi suara positif pada jawaban ini karena jawaban yang diterima tidak menghapus kode seperti yang diharapkan dan sintaks seperti preprocessor lebih cenderung diidentifikasi sebagai elemen bersyarat.
Christian Ivicevic
1
Terima kasih banyak! Ini bekerja seperti pesona. Beberapa jam percobaan dengan ContextReplacementPlugin, NormalModuleReplacementPlugin, dan hal lainnya - semuanya gagal. Dan inilah ifdef-loader, menghemat hari saya.
jeron-diovis
28

Saya akhirnya menggunakan sesuatu yang mirip dengan Jawaban Matt Derrick , tetapi khawatir tentang dua poin:

  1. Konfigurasi lengkap disuntikkan setiap kali saya menggunakan ENV(Yang buruk untuk konfigurasi besar).
  2. Saya harus menentukan beberapa titik masuk karena require(env)menunjuk ke file yang berbeda.

Apa yang saya dapatkan adalah komposer sederhana yang membangun objek konfigurasi dan memasukkannya ke modul konfigurasi.
Berikut adalah struktur file yang saya gunakan untuk ini:

config/
 └── main.js
 └── dev.js
 └── production.js
src/
 └── app.js
 └── config.js
 └── ...
webpack.config.js

Ini main.jsmenampung semua hal konfigurasi default:

// main.js
const mainConfig = {
  apiEndPoint: 'https://api.example.com',
  ...
}

module.exports = mainConfig;

Satu dev.js- production.jssatunya penyimpanan config yang menggantikan konfigurasi utama:

// dev.js
const devConfig = {
  apiEndPoint: 'http://localhost:4000'
}

module.exports = devConfig;

Bagian yang penting adalah webpack.config.jsyang menyusun konfigurasi dan menggunakan DefinePlugin untuk menghasilkan variabel lingkungan __APP_CONFIG__yang menampung objek config yang disusun:

const argv = require('yargs').argv;
const _ = require('lodash');
const webpack = require('webpack');

// Import all app configs
const appConfig = require('./config/main');
const appConfigDev = require('./config/dev');
const appConfigProduction = require('./config/production');

const ENV = argv.env || 'dev';

function composeConfig(env) {
  if (env === 'dev') {
    return _.merge({}, appConfig, appConfigDev);
  }

  if (env === 'production') {
    return _.merge({}, appConfig, appConfigProduction);
  }
}

// Webpack config object
module.exports = {
  entry: './src/app.js',
  ...
  plugins: [
    new webpack.DefinePlugin({
      __APP_CONFIG__: JSON.stringify(composeConfig(ENV))
    })
  ]
};

Langkah terakhir sekarang adalah config.js, terlihat seperti ini (Menggunakan sintaks ekspor impor es6 di sini karena di bawah webpack):

const config = __APP_CONFIG__;

export default config;

Di Anda, app.jsAnda sekarang bisa menggunakan import config from './config';untuk mendapatkan objek konfigurasi.

rumah
sumber
2
Benar-benar jawaban terbaik di sini
Gabriel
18

cara lain adalah menggunakan file JS sebagai proxy, dan membiarkan file itu memuat modul yang diinginkan commonjs, dan mengekspornya sebagai es2015 module, seperti ini:

// file: myModule.dev.js
module.exports = "this is in dev"

// file: myModule.prod.js
module.exports = "this is in prod"

// file: myModule.js
let loadedModule
if(WEBPACK_IS_DEVELOPMENT){
    loadedModule = require('./myModule.dev.js')
}else{
    loadedModule = require('./myModule.prod.js')
}

export const myString = loadedModule

Kemudian Anda dapat menggunakan modul ES2015 di aplikasi Anda secara normal:

// myApp.js
import { myString } from './store/myModule.js'
myString // <- "this is in dev"
Alejandro Silva
sumber
19
Satu-satunya masalah dengan if / else dan require adalah bahwa kedua file yang diperlukan akan digabungkan ke dalam file yang dihasilkan. Saya belum menemukan solusi. Pada dasarnya bundling terjadi terlebih dahulu, kemudian mangling.
alex
2
Itu tidak perlu benar, jika Anda menggunakan plugin dalam file webpack Anda webpack.optimize.UglifyJsPlugin(), pengoptimalan webpack tidak akan memuat modul, karena kode baris di dalam kondisional selalu salah, jadi webpack menghapusnya dari bundel yang dihasilkan
Alejandro Silva
@AlejandroSilva apakah Anda memiliki contoh repo ini?
Kapusin
1
@thevangelist yep: github.com/AlejandroSilva/mototracker/blob/master/… itu node + react + redux pet proyect: P
Alejandro Silva
4

Menghadapi masalah yang sama dengan OP dan diperlukan, karena lisensi, untuk tidak menyertakan kode tertentu dalam build tertentu, saya mengadopsi webpack-conditional-loader sebagai berikut:

Dalam perintah build saya, saya menetapkan variabel lingkungan secara tepat untuk build saya. Misalnya 'demo' di package.json:

...
  "scripts": {
    ...
    "buildDemo": "./node_modules/.bin/webpack --config webpack.config/demo.js --env.demo --progress --colors",
...

Sedikit membingungkan yang hilang dari dokumentasi yang saya baca adalah bahwa saya harus membuatnya terlihat selama proses pembuatan dengan memastikan variabel env saya dimasukkan ke dalam proses global sehingga di webpack.config / demo.js saya:

/* The demo includes project/reports action to access placeholder graphs.
This is achieved by using the webpack-conditional-loader process.env.demo === true
 */

const config = require('./production.js');
config.optimization = {...(config.optimization || {}), minimize: false};

module.exports = env => {
  process.env = {...(process.env || {}), ...env};
  return config};

Dengan ini, saya dapat mengecualikan apa pun secara kondisional, memastikan bahwa kode terkait apa pun diguncang dengan benar dari JavaScript yang dihasilkan. Misalnya di routes.js saya konten demo disimpan dari build lain sebagai berikut:

...
// #if process.env.demo
import Reports from 'components/model/project/reports';
// #endif
...
const routeMap = [
  ...
  // #if process.env.demo
  {path: "/project/reports/:id", component: Reports},
  // #endif
...

Ini bekerja dengan webpack 4.29.6.

Paul Whipp
sumber
1
Ada juga github.com/dearrrfish/preprocess-loader yang memiliki lebih banyak fitur
pengguna9385381
1

Saya kesulitan mengatur env di konfigurasi webpack saya. Yang biasanya saya inginkan adalah menyetel env agar dapat dijangkau di dalam webpack.config.js, postcss.config.jsdan di dalam aplikasi titik masuk itu sendiri (index.js biasanya). Saya berharap temuan saya dapat membantu seseorang.

Solusi yang saya dapatkan adalah dengan meneruskan --env productionatau --env development, dan kemudian mengatur mode di dalam webpack.config.js. Namun, itu tidak membantu saya membuat envdapat diakses di tempat yang saya inginkan (lihat di atas), jadi saya juga perlu menyetel process.env.NODE_ENVsecara eksplisit, seperti yang direkomendasikan di sini . Bagian paling relevan yang saya miliki di webpack.config.jsikuti di bawah ini.

...
module.exports = mode => {
  process.env.NODE_ENV = mode;

  if (mode === "production") {
    return merge(commonConfig, productionConfig, { mode });
  }
  return merge(commonConfig, developmentConfig, { mode });
};
Max
sumber
0

Gunakan variabel lingkungan untuk membuat penerapan dev dan prod:

https://webpack.js.org/guides/environment-variables/

Simon H.
sumber
2
Ini bukan yang saya tanyakan
Dominic
masalahnya adalah webpack akan mengabaikan kondisi saat membangun bundel dan akan menyertakan kode yang dimuat untuk pengembangan ... jadi itu tidak menyelesaikan masalah
sergioviniciuss
-1

Meskipun ini bukan solusi terbaik, ini mungkin berhasil untuk beberapa kebutuhan Anda. Jika Anda ingin menjalankan kode berbeda di node dan browser menggunakan ini berfungsi untuk saya:

if (typeof window !== 'undefined') 
    return
}
//run node only code now
Esqarrouth
sumber
1
OP menanyakan tentang keputusan waktu kompilasi, jawaban Anda tentang waktu proses.
Michael