React全家桶構建一款Web音樂App實戰(一):專案準備

code_mcx發表於2017-12-18

前言:相信大家看到過不少web音樂app,什麼仿網易雲、QQ音樂之類的。筆者帶大家自制一款web音樂app

本專案使用react全家桶打造,技術綜合性比較高。很多東西不會從基礎講解

技術要求

  1. react (版本16.2.0,基礎熟練)
  2. react-router (4.2.2,掌握基礎)
  3. react-redux (5.0.6,掌握基礎)
  4. es6 (掌握基礎)
  5. webpack (3.8.1,掌握基礎)
  6. html5
  7. css3

建立專案

使用create-react-app建立專案基本骨架,然後安裝路由(由於4.x的版本路由變化比較大。路由安裝react-router-dom 4.2.2的版本即可)

建立後的專案結構如下

React全家桶構建一款Web音樂App實戰(一):專案準備

如何使用自己的webpack配置?

在react腳手架中webpack基本的配置官方都已經給我們配置好了,那麼如何加入我們自定義的配置檔案呢,這裡使用一個rewire模組。 首先安裝rewire 模組

npm install rewire proxyquire --save-dev
複製程式碼

rewire只需要在專案開發時打包使用,安裝到開發依賴即可

編寫配置檔案

在專案的根目錄下建立scripts資料夾,然後新建一個customized-build.js檔案 程式碼如下:

/*
  本模組執行react-scripts裡的指令碼 (Create React App)
  可以自定義webpack配置,通過在專案根目錄建立"overrides-config.dev.js""overrides-config.prod.js" 檔案.

  A config-overrides file should export a single function that takes a
  config and modifies it as necessary.

  module.exports = function(webpackConfig) {
    webpackConfig.module.rules[0].use[0].options.useEslintrc = true;
  };
*/
var rewire = require('rewire');
var proxyquire = require('proxyquire');

switch(process.argv[2]) {
  // The "start" script is run during development mode
  case 'start':
    rewireModule('react-scripts/scripts/start.js', loadCustomizer('./overrides-config.dev'));
    break;
  // The "build" script is run to produce a production bundle
  case 'build':
    rewireModule('react-scripts/scripts/build.js', loadCustomizer('./overrides-config.prod'));
    break;
  // The "test" script runs all the tests with Jest
  case 'test':
    // Load customizations from the config-overrides.testing file.
    // That file should export a single function that takes a config and returns a config
    let customizer = loadCustomizer('./overrides-config.testing');
    proxyquire('react-scripts/scripts/test.js', {
      // When test.js asks for '../utils/createJestConfig' it will get this instead:
      '../utils/createJestConfig': (...args) => {
        // Use the existing createJestConfig function to create a config, then pass
        // it through the customizer
        var createJestConfig = require('react-scripts/utils/createJestConfig');
        return customizer(createJestConfig(...args));
      }
    });
    break;
  default:
    console.log('customized-build only supports "start", "build", and "test" options.');
    process.exit(-1);
}

// Attempt to load the given module and return null if it fails.
function loadCustomizer(module) {
  try {
    return require(module);
  } catch(e) {
    if(e.code !== "MODULE_NOT_FOUND") {
      throw e;
    }
  }

  // If the module doesn't exist, return a
  // noop that simply returns the config it's given.
  return config => config;
}

function rewireModule(modulePath, customizer) {
  // Load the module with `rewire`, which allows modifying the
  // script's internal variables.
  let defaults = rewire(modulePath);

  // Reach into the module, grab its global 'config' variable,
  // and pass it through the customizer function.
  // The customizer should *mutate* the config object, because
  // react-scripts imports the config as a `const` and we can't
  // modify that reference.
  let config = defaults.__get__('config');
  customizer(config);
}
複製程式碼

上述程式碼的作用就是在執行時獲取dev、build期間對應模組配置。react腳手架自帶的指令碼 npm run start,npm run build執行的webpack配置檔案 存放在node_modules/react-scripts/config下面

React全家桶構建一款Web音樂App實戰(一):專案準備
webpack.config.dev.js開發時的配置檔案

webpack.config.prod.js生產打包時的配置檔案

function rewireModule(modulePath, customizer) {
  // Load the module with `rewire`, which allows modifying the
  // script's internal variables.
  let defaults = rewire(modulePath);

  // Reach into the module, grab its global 'config' variable,
  // and pass it through the customizer function.
  // The customizer should *mutate* the config object, because
  // react-scripts imports the config as a `const` and we can't
  // modify that reference.
  let config = defaults.__get__('config');
  customizer(config);
}
複製程式碼

上述程式碼中的config就是webpack.config.dev.js(開發時)或webpack.config.prod.js(生產打包時)中module.exports的物件。有了這個物件我們可以對它做新增配置的操作

配置Stylus預處理語言

因為stylus就是為了node環境而打造,語法和css幾乎相差不大。所有該專案使用stylus作為css預處理語言。stylus的github地址:github.com/stylus/styl…

首先安裝stylus模組

npm install stylus stylus-loader --save-dev
複製程式碼

模組安裝完成後,在scripts資料夾下面新建overrides-config.base.js檔案,同時為開發、和生產打包時建立overrides-config.dev.jsoverrides-config.prod.js檔案

overrides-config.dev.js

module.exports = function(config) {

    // Use your ESLint
    /*let eslintLoader = config.module.rules[0];
    eslintLoader.use[0].options.useEslintrc = true;*/

    // Add the stylus loader second-to-last
    // (last one must remain as the "file-loader")
    let loaderList = config.module.rules[1].oneOf;
    loaderList.splice(loaderList.length - 1, 0, {
        test: /\.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"]
    });
};
複製程式碼

overrides-config.prod.js

const paths = require('react-scripts/config/paths');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath;
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
const cssFilename = 'static/css/[name].[contenthash:8].css';
const extractTextPluginOptions = shouldUseRelativeAssetPaths
    ? // Making sure that the publicPath goes back to to build folder.
    { publicPath: Array(cssFilename.split('/').length).join('../') }
    : {};

module.exports = function(config) {

    // Use your ESLint
    /*let eslintLoader = config.module.rules[0];
    eslintLoader.use[0].options.useEslintrc = true;*/

    // Add the stylus loader second-to-last
    // (last one must remain as the "file-loader")
    let loaderList = config.module.rules[1].oneOf;
    loaderList.splice(loaderList.length - 1, 0, {
        test: /\.styl$/,
        loader: ExtractTextPlugin.extract(
            Object.assign(
                {
                    fallback: {
                        loader: require.resolve('style-loader'),
                        options: {
                            hmr: false
                        }
                    },
                    use: [
                        {
                            loader: require.resolve('css-loader'),
                            options: {
                                importLoaders: 1,
                                minimize: true,
                                sourceMap: true
                            }
                        },
                        {
                            loader: require.resolve('stylus-loader')
                        }
                    ]
                }
            ), extractTextPluginOptions)
    });
};
複製程式碼

修改啟動指令碼

開啟package.json檔案

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
複製程式碼

將start和build指令碼修改如下

"scripts": {
    "start": "node scripts/customized-build start",
    "build": "node scripts/customized-build build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
複製程式碼

測試webpack配置

將專案下的App.css修改成App.styl,去掉裡面的花括號和分號

.App 
  text-align: center


.App-logo 
  animation: App-logo-spin infinite 20s linear
  height: 80px


.App-header 
  background-color: #222
  height: 150px
  padding: 20px
  color: white


.App-title 
  font-size: 1.5em


.App-intro 
  font-size: large


@keyframes App-logo-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
複製程式碼

執行命令npm start,此時執行的是自定義的配置檔案

React全家桶構建一款Web音樂App實戰(一):專案準備
檢視地址http://localhost:3000頁面上內容是否與第一次建立時是否一致,如果沒有任何變化說明沒有問題

加入autoprefixer

autoprefixer是用來處理css廠商字首的一款外掛。react腳手架已經依賴了autoprefixer外掛,所以npm已經為我們安裝了autoprefixer

為了stylus和autoprefixer一起使用這裡使用一款poststylus外掛,github地址:github.com/seaneking/p…。安裝poststylus

npm install poststylus --save-dev
複製程式碼

overrides-config.base.js配置檔案,增加poststylus外掛配置

const webpack = require('webpack');
const poststylus = require('poststylus');
const autoprefixer = require('autoprefixer');
複製程式碼
module.exports.stylusLoaderOptionsPlugin = new webpack.LoaderOptionsPlugin({
    options: {
      stylus: {
        use: [
          poststylus([ 
            require('postcss-flexbugs-fixes'),
            autoprefixer({
              browsers: [
                '>1%',
                'last 4 versions',
                'Firefox ESR',
                'not ie < 9', // React doesn't support IE8 anyway
              ],
              flexbox: 'no-2009',
            })
          ])
        ]
      }
    }
  });
複製程式碼

然後在overrides-config.dev.jsoverrides-config.prod.js中匯入overrides-config.base.js,在model.exports函式中增加以下程式碼

// Use Poststylus Plugin to handle stylus
config.plugins.push(baseConfig.stylusLoaderOptionsPlugin);
複製程式碼

未使用poststylus外掛時,我們檢視Logo圖片的樣式

React全家桶構建一款Web音樂App實戰(一):專案準備

使用後

React全家桶構建一款Web音樂App實戰(一):專案準備

已經為我們自動新增了字首

為專案配置根路徑別名

在使用相對路徑的時候如果層級很深會非常麻煩,這個時候配置根路徑別名就很方便的使用絕對路徑了 在overrides-config.base.js中增加以下程式碼

const path = require('path');
複製程式碼
function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

module.exports.rootPath = resolve('src');
複製程式碼

最後在overrides-config.dev.js和overrides-config.prod.js的model.exports函式中增加

// Define the root path alias
let alias = config.resolve.alias;
alias["@"] = baseConfig.rootPath;
複製程式碼

overrides-config.base.js完整配置如下

const path = require('path');
const webpack = require('webpack');
const poststylus = require('poststylus');
const autoprefixer = require('autoprefixer');

function resolve (dir) {
    return path.join(__dirname, '..', dir)
}

module.exports.rootPath = resolve('src');

module.exports.stylusLoaderOptionsPlugin = new webpack.LoaderOptionsPlugin({
    options: {
        stylus: {
            use: [
                poststylus([
                    require('postcss-flexbugs-fixes'),
                    autoprefixer({
                        browsers: [
                            '>1%',
                            'last 4 versions',
                            'Firefox ESR',
                            'not ie < 9', // React doesn't support IE8 anyway
                        ],
                        flexbox: 'no-2009',
                    })
                ])
            ]
        }
    }
});
複製程式碼

結語

後續更新中...

完整專案地址:github.com/code-mcx/ma…

本章節程式碼在chapter1分支

相關文章