The original link

Demand background

to complete deployment before embedding code into the tag of a client website, encountered a number of difficulties in development

Demand the difficulty

Selection of packaging tool

The easiest solution is to stick with the company’s old WebPack framework, which has been upgraded to WebPack4 (see webPack Configuration Notes in the previous article), which is not optimal because it may contain some extra WebPack code. However, due to the pressure of immature technology and development time, webpack is taken as the selection. There is a need for post-optimization, considering schemes such as rollup.js, jQuery, VanillaJS (native JS)

Packaging tool Refactoring

There is a need to package all the code into a js file, so there are some changes to the framework. The changes are listed below, and the ones highlighted in red are the most important

webpack.base.babel.js

// const CopyWebpackPlugin = require('copy-webpack-plugin'); Plugin {test: /\.(jpe? G | PNG | | GIF SVG) $/, use: [{loader: 'url - loader, the options: {/ / the Inline files smaller than 10 kB, as far as possible to all of the images Inline to js limit: 100 * 1024, }, }, ], }, new webpack.optimize.MinChunkSizePlugin({ minChunkSize: Infinity, // Minimum number of characters, try to pack js into a file}), // New CopyWebpackPlugin([// 'app/favicon.ico', // 'app/manifest.json', //]. Map ((SRC) => ({from: SRC, to: path.resolve(process.cwd(), 'build') }))),Copy the code

webpack.prod.babel.js

// const LodashModuleReplacementPlugin = require('lodash-webpack-plugin'); Measurement has an impact on packaging into a JS, and the cause has not been found yet

cssDebug: true.// Enable cssDebug to avoid using MiniCssExtractPlugin, which may generate redundant CSS
entry: './app/app.js'.// Change the entry without subcontracting
output: {
  filename: 'bundle.js'.// Output only one bundle.js file
},

splitChunks: false.// Turn off subcontracting optimization, same as below
runtimeChunk: false./ / new LodashModuleReplacementPlugin (), / / lodash optimization, temporary shielding, ditto
/ / new webpack. HashedModuleIdsPlugin (), / / do not need fixed chunkhash, because only one file
Copy the code

That’s how you package all your code into a bundle.js file

Bundle.js inserts the render node

The original approach was to write div tags to render in index.html, but since it is not possible to write div tags in other people’s HTML when introduced to the client, the solution is as follows

The main changes are made to the app.js entry file by actively creating a div node at the end of the body tag when rendering app.js

const render = () = > {
  const root = document.createElement('div');
  document.querySelector('body').appendChild(root);
  ReactDOM.render(
    <App />,
    root
  );
};

render();
Copy the code

Bundle. js introduces babel-polyfill twice

If the client also references babel-polyfill, bundle.js will fail

if (!global._babelPolyfill) { // Prevent secondary introductions
  require('babel-polyfill');
}
Copy the code

Error bundle.js causes the entire client to crash

The ErrorBoundary component needs to be introduced and even if an error is reported only the bundle.js chat window introduced crashes

import React from 'react';
import PropTypes from 'prop-types';

class ErrorBoundary extends React.Component {
  static propTypes = {
    children: PropTypes.node,
  };

  constructor(props) {
    super(props);
    this.state = { error: null.errorInfo: null };
  }

  componentDidCatch(error, errorInfo) {
    console.log(error, errorInfo);
    // Display fallback UI
    this.setState({ error, errorInfo });
    // You can also log the error to an error reporting service
  }

  render() {
    // if (this.state.hasError) {
    // // You can render any custom fallback UI
    // return 

Something went wrong.

;
// } return this.props.children; }}export default ErrorBoundary; Copy the code

The ErrorBoundary is then wrapped around the node that is expected to report an error

Modify the theme color

As style variables given by the backend need to be read, js variables need to be read from the CSS style. After looking for a lot of information, it is found that the ideal style is styled- Components, which can read variables from the inline CSS

However, as the size of bundle.js has reached about 1.6m, introducing styled components will be even larger, so for the moment, the style style can only be written in the code. Of course, this cannot be implemented, such as changing the CSS style in hover state. There is no good solution to this problem

Nginx-related Settings

Set cookies across domains

Since the client is an unknown domain name, it involves the problem of setting cookies across domains, which can be achieved only when the front and back ends are set simultaneously

Background knowledge

CORS divides HTTP requests into two categories, and different categories negotiate cross-domain resource sharing based on different policies.

  1. Simple cross-domain requests

Browsers consider a simple cross-domain request when an HTTP request occurs in one of two ways:

  1. The request method is GET, HEAD, or POST, and when the request method is POST, Content-type must be a value in Application/X-www-form-urlencoded, multipart/form-data, or Text /plain.
  2. There is no custom HTTP header in the request.

For simple cross-domain requests, all the browser has to do is add the Origin Header to the HTTP request, populate the domain where the JavaScript script resides, and request resources from servers in other domains. After receiving a simple cross-domain request, the server adds the Access-Control-Allow-Origin Header to the response Header based on the resource permission configuration. When the browser receives the response, it looks at the Access-Control-allow-Origin Header and returns the result to JavaScript if the current domain is authorized. Otherwise, the browser ignores the response.

  1. Are but a Preflighted cross – domain request

But are there two circumstances surrounding an HTTP request which the browser considers a Preflighted cross domain request:

  1. In addition to GET, HEAD and POST(only with application/ X-www-form-urlencoded, multipart/form-data, HTTP methods other than text/plain Content-type).
  2. Custom HTTP headers appear in the request.

Surrounding a Preflighted cross-domain request involves a browser sending a Preflighted request before a real HTTP request is sent, to check whether the server does not support a real request but a OPTIONS precheck request. The Access Control-request-method Header and access-Control-request-headers Headers are used to describe the actual Request. The browser will also add the Origin Header. After the server receives the precheck request, configure it according to the resource permissions. Add access-Control-allow-origin Headers, access-Control-allow-methods, and access-Control-allow-headers Headers to the response Header. Represents the domain, request method, and request header that allow cross-domain resource requests, respectively. In addition, the access-Control-max-age Header can be added to the server to allow the browser to use the negotiation result without sending the pre-check request for a specified period of time. The browser decides whether to proceed with a real request for cross-domain resource access based on the results returned by the OPTIONS request. The process is transparent to the caller of the real request.

XMLHttpRequest supports carrying identity information (Credential, such as cookies or HTTP authentication information) across domain requests through the withCredentials attribute. When the browser sends a request carrying the Cookie Header to the server, if the server does not respond to the Access-Control-allow-credentials Header, the browser ignores the response.

The HTTP request in question is made by an Ajax XMLHttpRequest object, and all CORS HTTP request headers can be filled by the browser without setting them in the XMLHttpRequest object. The following HTTP headers, as defined by the CORS protocol, are used to negotiate when browsers make cross-domain resource requests:

  1. Origin. HTTP request headers, which must be carried by any request involving CORS.
  2. Access – Control – Request – Method. But are not known HTTP request headers, a way of expressing a real request in a Preflighted cross domain request.
  3. Access – Control – Request – Headers. But are not known HTTP request headers, a custom Header list used in a Preflighted cross-domain request to represent a real request.
  4. Access – Control – Allow – Origin. HTTP response header that specifies the source domain that the server side allows for cross-domain resource access. The wildcard * can be used to indicate that JavaScript from any domain is allowed to Access the resource, but access-Control-Allow-Origin must specify a specific domain and cannot use the wildcard when responding to an HTTP request carrying Credential information.
  5. Access – Control – Allow – the Methods. HTTP response header, which specifies the list of request methods that the server allows to access resources across domains. It is typically used in response to precheck requests.
  6. Access – Control – Allow – Headers. HTTP response headers, a list of request headers that the server allows to access resources across domains, typically used in response to precheck requests.
  7. Access – Control – Max – Age. HTTP response header, used in response to a precheck request, indicating the validity time of the precheck response. During this time, the browser can decide whether it is necessary to send the real request directly, without sending the precheck request again, based on the result of the negotiation.
  8. Access – Control – Allow – Credentials. The HTTP response header, which does not return access-Control-allow-credentials :true, is ignored by the browser.

The back-end

Set an alias to a file that can be accessed via /im
location ^~ /im { 
  alias /home/frontend/pony/;
}
/bundle.js /bundle.js /bundle.js
location = /bundle.js {
  alias /home/frontend/pony/bundle.js;
}
Anti-generation Settings cross domains and allow cookies to be set
location /service-tp-api/ {
  add_header Access-Control-Allow-Credentials true;  # whether to allow Cookie sending
  add_header Access-Control-Allow-Origin $http_origin; $http_origin is the domain name of the website that references bundle.js. It cannot be set to *, otherwise the cookie will not be set successfully
  add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; # which HTTP methods are used
  add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Coo kie,Set-Cookie,x-requested-with,content-type,pragma'; # Allow the header field to be used, you can view your request to add

  if ($request_method = 'OPTIONS') {
    # Cross-domain precheck
    return 204;
  }

  # Where is the reverse generation
  proxy_pass http://localhost:10602/api/im-service/;
  proxy_redirect off;
  proxy_set_header Host $host;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
}
Copy the code

The front end

The main thing is to add the mode and credentials fields to the fetch method, or set withCredentials in the case of XHR

export function createDebugUserInfo(data) {
  return httpFetch('/service-tp-api/create-debug-user', {
    method: 'POST',
    data,
    mode: 'cors'.credentials: 'include'}); }Copy the code

The above update is updated at 2019-5-11 17:52:48


Cursor position read writes

It is important to read and write the cursor position in order to achieve the effect of inserting an intermediate label or a newline character. Otherwise, the cursor will “flit”.

Read cursor position

function getCursorPosition(textDom) {
  let cursorPos = 0;
  if (document.selection) {
      // IE Support
    textDom.focus();
    const selectRange = document.selection.createRange();
    selectRange.moveStart('character', -textDom.value.length);
    cursorPos = selectRange.text.length;
  } else if (textDom.selectionStart || textDom.selectionStart === 0) {
      // Firefox support
    cursorPos = textDom.selectionStart;
  }
  return cursorPos;
}
Copy the code

Write cursor position

function setCursorPosition(elem, index) {
  const val = elem.value;
  const len = val.length;

  // If the length of the text exceeds, return directly
  if (len < index) return;
  // Note that setTimeout delays writing, otherwise it does not take effect
  setTimeout(() = > {
    elem.focus();
    if (elem.setSelectionRange) { // Standard browser
      elem.setSelectionRange(index, index);
    } else { // IE9-
      const range = elem.createTextRange();
      range.moveStart('character', -len);
      range.moveEnd('character', -len);
      range.moveStart('character', index);
      range.moveEnd('character'.0); range.select(); }},0);
}
Copy the code

The above update


Final effect