การสร้างเงื่อนไขตามสภาพแวดล้อมโดยใช้ Webpack


95

ฉันมีบางอย่างสำหรับการพัฒนา - เช่นล้อเลียนซึ่งฉันไม่ต้องการขยายไฟล์บิวด์แบบกระจายของฉันด้วย

ใน RequireJS คุณสามารถส่งการกำหนดค่าในไฟล์ปลั๊กอินและต้องการสิ่งต่างๆตามเงื่อนไขนั้น

สำหรับ webpack ดูเหมือนจะไม่มีวิธีการทำเช่นนี้ ประการแรกในการสร้างการกำหนดค่ารันไทม์สำหรับสภาพแวดล้อมฉันได้ใช้help.aliasเพื่อทำการกำหนดค่าใหม่ขึ้นอยู่กับสภาพแวดล้อมเช่น:

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

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

จากนั้นเมื่อสร้างการกำหนดค่า webpack ฉันสามารถกำหนดได้แบบไดนามิกว่าไฟล์ใดenvsettingsชี้ไปที่ (เช่นwebpackConfig.resolve.alias.envsettings = './' + env)

อย่างไรก็ตามฉันต้องการทำสิ่งต่อไปนี้:

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

แต่เห็นได้ชัดว่าฉันไม่ต้องการสร้างไฟล์จำลองเหล่านั้นหากสภาพแวดล้อมไม่ได้เยาะเย้ย

ฉันสามารถกำหนดสิ่งที่ต้องการทั้งหมดด้วยตนเองให้กับไฟล์ Stub ได้โดยใช้ resolution.alias อีกครั้ง - แต่มีวิธีที่ทำให้รู้สึกแฮ็คน้อยลงหรือไม่?

มีความคิดอย่างไรที่ฉันสามารถทำได้? ขอบคุณ.


โปรดทราบว่าตอนนี้ฉันได้ใช้นามแฝงเพื่อชี้ไปที่ไฟล์ (ต้นขั้ว) ว่างเปล่าในสภาพแวดล้อมที่ฉันไม่ต้องการ (เช่นต้องใช้ ('mocks') จะชี้ไปที่ไฟล์ว่างในสภาพแวดล้อมที่ไม่ใช่การเยาะเย้ยดูเหมือนจะแฮ็กเล็กน้อย แต่มัน ผลงาน
โดมินิก

คำตอบ:


60

คุณสามารถใช้กำหนดปลั๊กอิน

ฉันใช้มันโดยทำอะไรง่ายๆเช่นนี้ในไฟล์สร้าง webpack ของคุณซึ่งenvเส้นทางไปยังไฟล์ที่ส่งออกวัตถุของการตั้งค่า:

// 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 };

แล้วสิ่งนี้ในรหัสของคุณ

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

มันจะดึงโค้ดนี้ออกจากไฟล์บิลด์ของคุณหากเงื่อนไขเป็นเท็จ คุณสามารถดูการทำงานWebpack สร้างตัวอย่างที่นี่


ฉันสับสนเล็กน้อยกับวิธีแก้ปัญหานี้ ไม่ได้พูดถึงว่าฉันควรจะตั้งค่าenvอย่างไร เมื่อมองผ่านตัวอย่างนั้นดูเหมือนว่าพวกเขากำลังจัดการธงนั้นผ่านทางอึกและยาร์กซึ่งไม่ใช่ทุกคนที่ใช้
Andre

1
สิ่งนี้ทำงานอย่างไรกับผ้าสำลี? คุณต้องกำหนดตัวแปรส่วนกลางใหม่ด้วยตนเองที่เพิ่มในปลั๊กอิน Define หรือไม่?
ทำเครื่องหมาย

2
@ เครื่องหมายใช่ เพิ่มสิ่งที่ต้องการ"globals": { "ENV": true }ใน. eslintrc ของคุณ
Matt Derrick

ฉันจะเข้าถึงตัวแปร ENV ในคอมโพเนนต์ได้อย่างไร ฉันลองวิธีแก้ปัญหาข้างต้น แต่ฉันยังคงได้รับข้อผิดพลาดที่ไม่ได้กำหนด ENV
jasan

20
ไม่ดึงโค้ดออกจากไฟล์สร้าง! ฉันทดสอบแล้วและรหัสอยู่ที่นี่
Lionel

42

ไม่แน่ใจว่าทำไมคำตอบ "webpack.DefinePlugin" จึงเป็นคำตอบอันดับต้น ๆ สำหรับการกำหนดการนำเข้า / ต้องใช้ตามสภาพแวดล้อม

ปัญหาด้วยวิธีการที่เป็นที่คุณยังคงส่งมอบโมดูลทั้งหมดเหล่านั้นไปยังลูกค้า -> ตรวจสอบกับwebpack-กำ-analyezerเช่น และไม่ลดขนาด bundle.js ของคุณเลย :)

ดังนั้นสิ่งที่ทำงานได้ดีและมีเหตุผลมากกว่านั้นคือNormalModuleReplacementPlugin

ดังนั้นแทนที่จะทำ on_client conditional require -> เพียงแค่ไม่รวมไฟล์ที่ไม่จำเป็นลงในบันเดิลตั้งแต่แรก

หวังว่าจะช่วยได้


Nice ไม่รู้เกี่ยวกับปลั๊กอินนั้น!
Dominic

ในสถานการณ์นี้คุณจะไม่มีหลายบิลด์ต่อสภาพแวดล้อมหรือไม่ ตัวอย่างเช่นถ้าฉันมีที่อยู่บริการบนเว็บสำหรับสภาพแวดล้อม dev / QA / UAT / การผลิตฉันจะต้องใช้คอนเทนเนอร์แยกกัน 4 คอนเทนเนอร์ 1 สำหรับแต่ละสภาพแวดล้อม ตามหลักการแล้วคุณจะมีคอนเทนเนอร์เดียวและเปิดใช้งานด้วยตัวแปรสภาพแวดล้อมเพื่อระบุว่าจะโหลดการกำหนดค่าใด
Brett Mathe

ไม่ไม่จริงๆ นั่นคือสิ่งที่คุณทำกับปลั๊กอิน -> คุณระบุสภาพแวดล้อมของคุณผ่าน env vars และสร้างเพียงคอนเทนเนอร์เดียว แต่สำหรับสภาพแวดล้อมเฉพาะโดยไม่มีการรวมซ้ำซ้อน นอกหลักสูตรนั้นขึ้นอยู่กับว่าคุณตั้งค่าการกำหนดค่า webpack ของคุณอย่างไรและเห็นได้ชัดว่าคุณสามารถสร้างงานสร้างทั้งหมดได้ แต่ไม่ใช่สิ่งที่ปลั๊กอินนี้เกี่ยวกับและทำ
Roman Zhyliov

34

ใช้ifdef-loader. ในไฟล์ต้นฉบับของคุณคุณสามารถทำสิ่งต่างๆเช่น

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

การwebpackกำหนดค่าที่เกี่ยวข้องคือ

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}`,
        },
      },
    ],
  },
  // ...
};

3
ฉันเพิ่มคะแนนคำตอบนี้เนื่องจากคำตอบที่ยอมรับไม่ได้ตัดโค้ดออกตามที่คาดไว้และไวยากรณ์ที่คล้ายตัวประมวลผลก่อนมีแนวโน้มที่จะถูกระบุว่าเป็นองค์ประกอบที่มีเงื่อนไข
Christian Ivicevic

1
ขอบคุณมาก! มันใช้งานได้เหมือนมีเสน่ห์ หลายชั่วโมงของการทดลองกับ ContextReplacementPlugin, NormalModuleReplacementPlugin และสิ่งอื่น ๆ - ทั้งหมดล้มเหลว และนี่คือ ifdef-loader ช่วยประหยัดวันของฉัน
jeron-diovis

28

ฉันลงเอยด้วยการใช้สิ่งที่คล้ายกับคำตอบของ Matt Derrickแต่กังวลเกี่ยวกับสองประเด็น:

  1. การกำหนดค่าที่สมบูรณ์จะถูกแทรกทุกครั้งที่ฉันใช้ ENV (ซึ่งไม่ดีสำหรับการกำหนดค่าขนาดใหญ่)
  2. ฉันต้องกำหนดจุดเข้าใช้งานหลายจุดเนื่องจากrequire(env)ชี้ไปยังไฟล์ที่แตกต่างกัน

สิ่งที่ฉันคิดขึ้นมาคือนักแต่งเพลงอย่างง่ายซึ่งสร้างออบเจ็กต์ config และฉีดเข้าไปในโมดูล config
นี่คือโครงสร้างไฟล์ที่ Iam ใช้สำหรับสิ่งนี้:

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

main.jsถือทุกสิ่งที่การตั้งค่าเริ่มต้น:

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

module.exports = mainConfig;

dev.jsและproduction.jsสิ่งที่การตั้งค่าเพียงถือซึ่งแทนที่การตั้งค่าหลัก

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

module.exports = devConfig;

ส่วนที่สำคัญคือส่วนwebpack.config.jsที่ประกอบการกำหนดค่าและใช้DefinePluginเพื่อสร้างตัวแปรสภาพแวดล้อม__APP_CONFIG__ซึ่งเก็บวัตถุ config ที่ประกอบด้วย:

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))
    })
  ]
};

ขั้นตอนสุดท้ายคือตอนนี้config.jsดูเหมือนว่า (ใช้ es6 import export syntax ที่นี่เพราะอยู่ภายใต้ webpack):

const config = __APP_CONFIG__;

export default config;

ของคุณในapp.jsตอนนี้คุณสามารถใช้import config from './config';การได้รับวัตถุการตั้งค่า


2
คำตอบที่ดีที่สุดจริงๆที่นี่
Gabriel

18

อีกวิธีหนึ่งคือการใช้ไฟล์ JS เป็นไฟล์proxyและปล่อยให้ไฟล์นั้นโหลดโมดูลที่สนใจcommonjsและส่งออกเป็นes2015 moduleดังนี้:

// 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

จากนั้นคุณสามารถใช้โมดูล ES2015 ในแอปของคุณได้ตามปกติ:

// myApp.js
import { myString } from './store/myModule.js'
myString // <- "this is in dev"

19
ปัญหาเดียวของ if / else และ require คือไฟล์ที่ต้องการทั้งสองไฟล์จะรวมอยู่ในไฟล์ที่สร้างขึ้น ฉันไม่พบวิธีแก้ปัญหา การรวมกลุ่มโดยพื้นฐานเกิดขึ้นก่อนจากนั้นจึงทำให้แหลกเหลว
alex

2
นั่นไม่จำเป็นหากคุณใช้ปลั๊กอิน webpack ในไฟล์ webpack webpack.optimize.UglifyJsPlugin()การเพิ่มประสิทธิภาพของ webpack จะไม่โหลดโมดูลเนื่องจากโค้ดบรรทัดภายในเงื่อนไขเป็นเท็จเสมอดังนั้น webpack จึงลบออกจากบันเดิลที่สร้างขึ้น
Alejandro Silva

@AlejandroSilva คุณมีตัวอย่าง repo นี้หรือไม่?
Capuchin

1
@thevangelist ครับ: github.com/AlejandroSilva/mototracker/blob/master/… It's a node + react + redux pet proyect: P
Alejandro Silva

4

ประสบกับปัญหาเดียวกันกับ OP และจำเป็นเนื่องจากการออกใบอนุญาตไม่รวมรหัสบางอย่างในบางรุ่นฉันจึงใช้webpack-conditional-loaderดังนี้:

ในคำสั่ง build ของฉันฉันตั้งค่าตัวแปรสภาพแวดล้อมอย่างเหมาะสมสำหรับบิลด์ของฉัน ตัวอย่างเช่น 'demo' ใน package.json:

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

บิตสับสนที่ขาดหายไปจากเอกสารที่ฉันอ่านคือฉันต้องทำให้สิ่งนี้มองเห็นได้ตลอดการประมวลผลบิลด์โดยตรวจสอบให้แน่ใจว่าตัวแปร env ของฉันถูกแทรกเข้าไปในกระบวนการ global ดังนั้นใน webpack.config / demo.js ของฉัน:

/* 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};

ด้วยเหตุนี้ฉันจึงสามารถยกเว้นสิ่งใด ๆ ตามเงื่อนไขได้เพื่อให้แน่ใจว่าโค้ดที่เกี่ยวข้องจะถูกเขย่าอย่างเหมาะสมจาก JavaScript ตัวอย่างเช่นในเส้นทาง js ของฉันเนื้อหาสาธิตจะถูกเก็บไว้ไม่ให้สร้างอื่น ๆ ดังนี้:

...
// #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
...

ใช้งานได้กับ webpack 4.29.6


1
นอกจากนี้ยังมีgithub.com/dearrrfish/preprocess-loaderซึ่งมีคุณสมบัติเพิ่มเติม
user9385381

1

ฉันมีปัญหากับการตั้งค่า env ในการกำหนดค่า webpack ของฉัน สิ่งที่ฉันมักจะต้องการเป็นชุด env เพื่อที่จะสามารถเข้าถึงได้ภายในwebpack.config.js, postcss.config.jsและภายในโปรแกรมจุดเริ่มต้นที่ตัวเอง ( index.jsปกติ) ฉันหวังว่าการค้นพบของฉันจะช่วยใครสักคนได้

วิธีการแก้ปัญหาที่ผมได้มาด้วยคือการผ่านใน--env productionหรือและภายในโหมดตั้งค่าแล้ว--env development webpack.config.jsแต่ที่ไม่ได้ช่วยผมด้วยทำให้envสามารถเข้าถึงได้ที่ฉันต้องการมัน (ดูด้านบน) ดังนั้นฉันยังต้องกำหนดprocess.env.NODE_ENVอย่างชัดเจนตามคำแนะนำของที่นี่ ส่วนที่เกี่ยวข้องมากที่สุดที่ฉันมีอยู่webpack.config.jsด้านล่าง

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

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

0

ใช้ตัวแปรสภาพแวดล้อมเพื่อสร้างการปรับใช้ dev และ prod:

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


2
นี่ไม่ใช่สิ่งที่ฉันถาม
Dominic

ปัญหาคือ webpack จะเพิกเฉยต่อเงื่อนไขเมื่อสร้างบันเดิลและจะรวมโค้ดที่โหลดสำหรับการพัฒนาอย่างไรก็ตามมันไม่สามารถแก้ปัญหาได้
sergioviniciuss

-1

แม้ว่านี่จะไม่ใช่วิธีแก้ปัญหาที่ดีที่สุด แต่ก็อาจเหมาะกับความต้องการของคุณ หากคุณต้องการเรียกใช้รหัสอื่นในโหนดและเบราว์เซอร์โดยใช้สิ่งนี้ได้ผลสำหรับฉัน:

if (typeof window !== 'undefined') 
    return
}
//run node only code now

1
OP กำลังถามเกี่ยวกับการตัดสินใจเวลารวบรวมคำตอบของคุณคือเวลาทำงาน
Michael
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.