Xây dựng có điều kiện dựa trên môi trường sử dụng Webpack


93

Tôi có một số thứ cần phát triển - ví dụ như các bản mô phỏng mà tôi không muốn làm cồng kềnh tệp bản dựng được phân phối của mình.

Trong RequestJS, bạn có thể chuyển một cấu hình trong một tệp plugin và yêu cầu những thứ dựa trên đó một cách tùy tiện.

Đối với webpack dường như không có cách nào để làm điều này. Đầu tiên để tạo cấu hình thời gian chạy cho một môi trường, tôi đã sử dụng giải quyết.alias để chỉ định lại một yêu cầu tùy thuộc vào môi trường, ví dụ:

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

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

Sau đó, khi tạo cấu hình webpack, tôi có thể tự động gán tệp envsettingstrỏ tới (ví dụ webpackConfig.resolve.alias.envsettings = './' + env).

Tuy nhiên, tôi muốn làm điều gì đó như:

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

Nhưng rõ ràng là tôi không muốn xây dựng trong các tệp giả đó nếu môi trường không phải là giả.

Tôi có thể đặt lại thủ công tất cả những yêu cầu đó vào một tệp sơ khai bằng cách sử dụng giải quyết lại.alias - nhưng có cách nào cảm thấy ít hack hơn không?

Bất kỳ ý tưởng làm thế nào tôi có thể làm điều đó? Cảm ơn.


Lưu ý rằng hiện tại tôi đã sử dụng bí danh để trỏ đến tệp trống (sơ khai) trên các môi trường mà tôi không muốn (ví dụ: request ('mocks') sẽ trỏ đến tệp trống trên env không phải giả lập. Có vẻ hơi khó nhưng nó hoạt động.
Dominic

Câu trả lời:


59

Bạn có thể sử dụng plugin xác định .

Tôi sử dụng nó bằng cách làm điều gì đó đơn giản như sau trong tệp xây dựng webpack của bạn, nơi envlà đường dẫn đến tệp xuất một đối tượng của cài đặt:

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

và sau đó điều này trong mã của bạn

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

Nó sẽ loại bỏ mã này ra khỏi tệp xây dựng của bạn nếu điều kiện là sai. Bạn có thể xem một ví dụ xây dựng Webpack đang hoạt động tại đây .


Tôi hơi bối rối trước giải pháp này. Nó không đề cập đến cách tôi phải thiết lập env. Nhìn qua ví dụ đó, có vẻ như họ đang xử lý cờ đó thông qua gulp và yargs mà không phải ai cũng sử dụng.
Andre

1
Làm thế nào điều này làm việc với xơ vải? Bạn có phải xác định thủ công các biến toàn cục mới được thêm vào plugin Xác định không?
đánh dấu

2
@ đánh dấu có. Thêm một cái gì đó giống như "globals": { "ENV": true }.eslintrc của bạn
Matt Derrick

làm cách nào để truy cập biến ENV trong một thành phần? Tôi đã thử giải pháp ở trên nhưng tôi vẫn gặp lỗi không xác định được ENV
jasan 09/09

18
Nó KHÔNG loại bỏ mã ra khỏi các tệp xây dựng! Tôi đã thử nghiệm nó và mã ở đây.
Lionel

42

Không chắc tại sao câu trả lời "webpack.DefinePlugin" là câu trả lời hàng đầu ở mọi nơi để xác định các yêu cầu / nhập khẩu dựa trên Môi trường.

Các vấn đề với cách tiếp cận đó là bạn vẫn đang cung cấp tất cả những mô-đun cho khách hàng -> kiểm tra với webpack-bó-analyezer ví dụ. Và hoàn toàn không giảm kích thước gói.js của bạn :)

Vì vậy, những gì thực sự hoạt động tốt và hợp lý hơn nhiều là: NormalModuleReplacementPlugin

Vì vậy, thay vì thực hiện yêu cầu có điều kiện on_client -> chỉ không bao gồm các tệp không cần thiết vào gói ngay từ đầu

Hy vọng điều đó sẽ giúp


Rất vui không biết về plugin đó!
Dominic

Với kịch bản này, bạn sẽ không có nhiều bản dựng cho mỗi môi trường? Ví dụ: nếu tôi có địa chỉ dịch vụ web cho môi trường dev / QA / UAT / production thì tôi sẽ cần 4 vùng chứa riêng biệt, 1 vùng chứa cho mỗi môi trường. Lý tưởng nhất là bạn nên có một vùng chứa và khởi chạy nó với một biến môi trường để chỉ định cấu hình nào sẽ tải.
Brett Mathe

Không thật sự lắm. Đó chính xác là những gì bạn làm với plugin -> bạn chỉ định môi trường của mình thông qua env vars và nó chỉ xây dựng một vùng chứa, nhưng đối với môi trường cụ thể mà không có bao gồm thừa. Tất nhiên điều đó cũng phụ thuộc vào cách bạn thiết lập cấu hình webpack của mình và rõ ràng là bạn có thể tạo tất cả các bản dựng, nhưng nó không phải là những gì plugin này nói về và làm.
Roman Zhyliov 20/09/17

34

Sử dụng ifdef-loader. Trong các tệp nguồn của mình, bạn có thể làm những việc như

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

webpackCấu hình có liên quan là

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

2
Tôi đã tán thành câu trả lời này vì câu trả lời được chấp nhận không loại bỏ mã như mong đợi và cú pháp giống bộ tiền xử lý có nhiều khả năng được xác định là phần tử điều kiện.
Christian Ivicevic

1
Cám ơn rất nhiều! Nó hoạt động như một say mê. Vài giờ thử nghiệm với ContextReplacementPlugin, NormalModuleReplacementPlugin và những thứ khác - tất cả đều thất bại. Và đây là ifdef-loader, tiết kiệm một ngày của tôi.
jeron-diovis 10/1218

27

Cuối cùng tôi đã sử dụng một thứ tương tự như Câu trả lời của Matt Derrick , nhưng lo lắng về hai điểm:

  1. Cấu hình hoàn chỉnh được đưa vào mỗi khi tôi sử dụng ENV(Điều này không tốt cho các cấu hình lớn).
  2. Tôi phải xác định nhiều điểm nhập vì require(env)trỏ đến các tệp khác nhau.

Những gì tôi nghĩ ra là một trình tổng hợp đơn giản, xây dựng một đối tượng cấu hình và đưa nó vào mô-đun cấu hình.
Đây là cấu trúc tệp, tôi đang sử dụng cho việc này:

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

Các main.jsnắm giữ tất cả những thứ cấu hình mặc định:

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

module.exports = mainConfig;

Các dev.jsproduction.jscấu hình thứ chỉ giữ mà đè cấu hình chính:

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

module.exports = devConfig;

Phần quan trọng là phần webpack.config.jstạo cấu hình và sử dụng DefinePlugin để tạo một biến môi trường __APP_CONFIG__chứa đối tượng cấu hình đã cấu hình:

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

Bước cuối cùng bây giờ là config.js, nó trông giống như thế này (Sử dụng cú pháp xuất nhập của es6 ở đây vì nó nằm dưới webpack):

const config = __APP_CONFIG__;

export default config;

Trong của app.jsbạn bây giờ bạn có thể sử dụng import config from './config';để lấy đối tượng cấu hình.


2
Thực sự là câu trả lời hay nhất ở đây
Gabriel

18

một cách khác là sử dụng tệp JS dưới dạng tệp proxyvà để tệp đó tải mô-đun quan tâm commonjsvà xuất nó es2015 modulenhư sau:

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

Sau đó, bạn có thể sử dụng mô-đun ES2015 trong ứng dụng của mình một cách bình thường:

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

19
Vấn đề duy nhất với if / else và request là cả hai tệp bắt buộc sẽ được gộp chung vào tệp được tạo. Tôi đã không tìm thấy một giải pháp thay thế. Về cơ bản, việc gộp nhóm xảy ra đầu tiên, sau đó mới đến việc xử lý.
alex

2
điều đó không cần thiết đúng, nếu bạn sử dụng plugin trong tệp webpack của mình webpack.optimize.UglifyJsPlugin(), thì việc tối ưu hóa webpack sẽ không tải mô-đun, vì mã dòng bên trong điều kiện luôn là false, vì vậy webpack hãy xóa nó khỏi gói đã tạo
Alejandro Silva

@AlejandroSilva bạn có ví dụ về repo này không?
thevangelist,

1
@thevangelist vâng: github.com/AlejandroSilva/mototracker/blob/master/... đó là một nút + phản ứng + Redux proyect vật nuôi: P
Alejandro Silva

4

Đối mặt với vấn đề tương tự như OP và được yêu cầu, do được cấp phép, không bao gồm mã nhất định trong các bản dựng nhất định, tôi đã sử dụng trình tải webpack-có điều kiện như sau:

Trong lệnh xây dựng của mình, tôi đặt một biến môi trường thích hợp cho bản dựng của mình. Ví dụ: 'demo' trong package.json:

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

Một chút khó hiểu bị thiếu trong tài liệu tôi đọc là tôi phải làm cho điều này hiển thị trong suốt quá trình xây dựng bằng cách đảm bảo biến env của tôi được đưa vào quy trình toàn cầu do đó trong webpack.config / demo.js của tôi:

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

Với điều này tại chỗ, tôi có thể loại trừ bất kỳ thứ gì có điều kiện, đảm bảo rằng bất kỳ mã liên quan nào được loại bỏ đúng cách khỏi JavaScript kết quả. Ví dụ: trong route.js của tôi, nội dung demo được giữ bên ngoài các bản dựng khác, do đó:

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

Điều này hoạt động với webpack 4.29.6.


1
Ngoài ra còn có github.com/dearrrfish/preprocess-loader có nhiều tính năng hơn
user9385381 29/04 '19

1

Tôi đã vật lộn với việc thiết lập env trong cấu hình webpack của mình. Những gì tôi thường muốn là để thiết lập env để nó có thể đạt được bên trong webpack.config.js, postcss.config.jsvà bên trong ứng dụng điểm vào bản thân ( index.jsthường). Tôi hy vọng rằng những phát hiện của tôi có thể giúp ích cho ai đó.

Giải pháp mà tôi đã đưa ra là chuyển vào --env productionhoặc --env developmentrồi đặt chế độ bên trong webpack.config.js. Tuy nhiên, điều đó không giúp tôi có envthể truy cập ở nơi tôi muốn (xem ở trên), vì vậy tôi cũng cần đặt process.env.NODE_ENVrõ ràng, như được khuyến nghị ở đây . Phần liên quan nhất mà tôi có trong webpack.config.jsphần dưới đây.

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

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


-1

Mặc dù đây không phải là giải pháp tốt nhất, nhưng nó có thể phù hợp với một số nhu cầu của bạn. Nếu bạn muốn chạy mã khác trong nút và trình duyệt bằng cách sử dụng điều này phù hợp với tôi:

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

1
OP đang hỏi về quyết định thời gian biên dịch, câu trả lời của bạn là về thời gian chạy.
Michael
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.