preface

Common JS plugins rarely use ES6 classes, instead wrapping a library with constructors and often handwritten CMD/AMD specifications, such as this:

/ / reference from: https://www.jianshu.com/p/e65c246beac1; (function(undefined) {
    "use strict"
    var _global;
    var plugin = {
      // ...
    }
    _global = (function() {return this || (0, eval) ('this'); } ());if(typeof module ! = ="undefined" && module.exports) {
        module.exports = plugin;
    } else if (typeof define === "function" && define.amd) {
        define(function() {returnplugin; }); }else {
        !('plugin' in _global) && (_global.plugin = plugin);
    }
}());
Copy the code

But now that it’s 9102 years old, it’s time to come up with our ES6 method, which can be written in a more elegant way to implement a library, like this:

class RememberScroll {
    constructor(options) { ... }}export default RememberScroll
Copy the code

In this article, I share a recent plugin I wrote to remember the scrolling position of a page, and show you how to use class syntax to package a usable library with WebPack 4.x and Babel 7.x.

Project address: Github, online Demo: Demo

My favorite friend would like to click a Star for collection. Thank you very much.

Source of demand

I believe many students have encountered such a requirement: after browsing a page and leaving, the user needs to re-locate to the last left location when opening it again.

This demand is very common. We usually have this function on the article page of wechat public account on our mobile phone. Want to achieve this requirement, it is also easier to achieve, but the blogger is a little lazy, thinking that there is no ready-made library can be directly used? I did a search on GitHub and found nothing good that met my needs, so I had to implement it myself.

In order to be flexible (only some pages need this feature), the blogger packaged the library separately in the project, which was originally used in the company project, but later thought why not open source? So there is this sharing, which is also a summary of my own work.

The desired effect

Bloggers like to imagine the expected effects before they do something. Bloggers want the library to be as simple to use as possible, preferably by inserting a line of code like this:

<html>
<head>
  <meta charset="utf-8">
  <title>remember-scroll examples</title>
</head>
<body>
  <div id="content"></div>
  <script src=".. /dist/remember-scroll.js"></script>
  <script>
    new RememberScroll()
  </script>
</body>
</html>
Copy the code

Just add a library to the page where you want to remember the user’s browsing location and initialize new RememberScroll().

Let’s take this goal and do it step by step.

Design scheme

1. What information do I need to save?

There are mainly two fields to save the position of the user browsing the page: which page and the position of leaving. Only by these two fields can we hit the page when the user opens the page of the website for the second time and automatically jump to the position of leaving last time.

2) Where does it exist?

Remember the browsing location. You need to record the browsing location of the user before leaving the browser on the client. These information can be stored in cookie, sessionStorage and localStorage.

  1. Stored in acookieIn 4K, the space is limited but barely ok. However, cookies are carried with each request to the server, potentially increasing bandwidth and server stress, so they are not suitable in general.
  2. Stored in asessionStorageThe user leaves the page because it is valid only for the current sessionsessionStorageIt gets wiped out, so it doesn’t meet our needs.
  3. Stored in alocalStorage, the browser can be permanently saved, the size is generally limited to 5M, to meet our needs.

To sum up, we should choose localStorage in the end.

3. Issues needing attention

  1. A site may have many pages. How do you identify which pages?

Generally speaking, the URL of a page can be used as the unique identification of a page, for example, www.xx.com/article/${id}. Different ids correspond to different pages.

But bloggers are concerned that many sites now use spa and often have hash # XXX at the end of their URLS, such as www.xx.com/article/${id}#tag1 and www.xx.com/article/${id}#tag2, This may represent different anchors on the same page, so using urls as unique identifiers for pages is not reliable.

Therefore, the blogger decided to use the unique page identifier as a parameter for the user to decide, let’s call it pageKey, let the user guarantee that the site is unique.

  1. If users visit many, many pages on our site, becauselocalStorageIt’s permanently preserved,How to avoid excessive accumulation of localStorage?

We may only want to remember recently, i.e. only a few days of the user’s browsing location, and we may prefer that the data we store expire automatically.

But localStorage itself is not automatic expiration mechanism, generally can only save the data at the same time save a timestamp, and then determine whether the expiration when using. If a new record is generated when a new page is accessed, at least one record will always exist in localStorage. In other words, automatic expiration cannot be implemented. Here can not help but feel a little redundant, since will always keep records in localStorage, that simply do not judge, let’s change the idea: only record a limited number of the latest page.

Here’s an example:

Our website has an article page: www.xx.com/articles/${id}. Each ID represents a different article. We only record the latest 5 articles visited by users, that is, maintain a queue with a length of 5.

For example, the current website has articles with id ranging from 1 to 100. When users visit the first 1,2,3,4 and 5 articles respectively, these 5 articles will record the location of leaving. When users open the sixth article, the sixth article will record joining the queue while the first one will record leaving the queue. The location of 2,3,4,5, and 6 articles is recorded in localStorage, which ensures that localStorage will never accumulate data and that old records will automatically “expire” as new pages are accessed.

To be more flexible, the blogger decided to add a parameter to the plugin called maxLength, which indicates the maximum number of pages recorded on the current site. The default value is set to 5.

4. Implementation idea

  1. We need to always listen to the user when browsing the page scrollbar position, can passwindow.onscrollEvent to get the current scroll bar position:scrollTop.
  2. willscrollTopAnd page unique identifierpageKeyintolocalStorageIn the.
  3. The user opens the previously visited page again and, when the page is initialized, readslocalStorageTo determine the page’s datapageKeyIf the page is consistent, the position of the scroll bar will be automatically rolled to the correspondingscrollTopValue.

Isn’t that easy? However, the implementation process needs to pay attention to the details, such as a shake – proof treatment.

Implementation steps

After all this pushing, it’s time to start coding.

1. The encapsulationlocalStorageUtility methods

To do a good job, he must sharpen his tools. In order to better serve the following work, let’s briefly encapsulate several methods to call localStorage, mainly get,set,remove:

// storage.js
const Storage = {
  isSupport () {
    if (window.localStorage) {
      return true
    } else {
      console.error('Your browser cannot support localStorage! ')
      return false
    }
  },
  get (key) {
    if(! this.isSupport) {return
    }
    const data = window.localStorage.getItem(key)
    return data ? JSON.parse(data) : undefined
  },
  remove (key) {
    if(! this.isSupport) {return
    }
    window.localStorage.removeItem(key)
  },
  set (key, data) {
    if(! this.isSupport) {return
    }
    const newData = JSON.stringify(data)
    window.localStorage.setItem(key, newData)
  }
}

export default Storage
Copy the code

2. Class solution

A class is essentially a function, but it is more intuitive to define a class using a class. Let’s name our library as a RememberScroll and it will look like this to start with:

import Storage from './storage'
class RememberScroll {
    constructor() {}}Copy the code

1. Process the passed parameters

We need to take the arguments in the class’s constructor and override the default arguments.

Remember our expected usage above? New RememberScroll({pageKey: ‘myPage’, maxLength: 10})

  constructor (options) {
    let defaultOptions = {
      pageKey: '_page1'MaxLength: 5} this.options = object. assign({}, defaultOptions, options)}Copy the code

If no arguments are passed, the default arguments are used; if they are passed, the passed arguments are used. This. Options is the final parameter.

2. Initialize the page

When the page is initialized, we need to do three things:

  • fromloaclStorageFetching cache list
  • Scroll the scroll bar to the recorded position (if any);
  • registeredwindow.onscrollEvents monitor user scrolling behavior. Therefore, it needs to be executed in the constructorinitScrollandaddScrollEventThese two methods:
import Storage from './utils/storage'
class RememberScroll {
  constructor (options) {
    // ...
    this.storageKey = '_rememberScroll'
    this.list = Storage.get(this.storageKey) || []
    this.initScroll()
    this.addScrollEvent()
  }
  initScroll() {/ /... }addScrollEvent() {/ /... }}Copy the code

Name the localStorage key _rememberScroll. This should avoid conflicts with the usual localStorage key names.

3. Listen to the implementation of addScrollEvent() for rolling events

  addScrollEvent() {window.onscroll = () => {// Get the latest position, Only to record the location of vertical const scrollTop = document. The documentElement. The scrollTop | | document. The body. The scrollTop / / the data for the current page object is const data = {  pageKey: this.options.pageKey, y: scrollTop }let index = this.list.findIndex(item => item.pageKey === data.pageKey)
      if(index >= 0) {this.list.splice(index, 1, data)}else{// If the length is exceeded, the earliest record is clearedif(this.list.length >= this.options.maxlength) {this.list.shift()} this.list.push(data)} // UpdatelocalStorage.set(this.storageKey, this.list)}}Copy the code

Ps: You’d better have a shake – proof treatment here

4. Initialize the scrollbar position: initScroll() implementation

initScroll() {// Check whether there is a recordif(this.list.length) {// Whether the current page pageKey is consistentlet currentPage = this.list.find(item => item.pageKey === this.options.pageKey)
      if (currentPage) {
        setTimeout(() => {window.scrollto (0, currentPage.y)}, 0)}}Copy the code

Careful students may notice that setTimeout is used instead of calling window.scrollto directly. This is because the blogger ran into a problem with the order in which the page was loaded.

Before window.scrollTo is executed, the page must already be loaded and the scrollbar must already exist. If the page is loaded directly, the scroll height may be 0, and window.scrollTo will be invalid. If the page’s data is retrieved asynchronously, window.scrollto will also be invalid. So setTimeout is a more stable way to do it.

5. Export the module

Finally, we need to export the module. The overall code looks something like this:

import Storage from './utils/storage'

class RememberScroll {
  constructor (options) {
    let defaultOptions = {
      pageKey: '_page1'MaxLength: 5} this.storageKey ='_rememberScroll'// This. options = object. assign({}, defaultOptions, Options) / / the cache list. This list = Storage. Get (enclosing storageKey) | | [] this. InitScroll () enclosing addScrollEvent ()}initScroll() {/ /... }addScrollEvent() {/ /... }}export default RememberScroll
Copy the code

This will basically complete the function of the plug-in, is not very simple ha ha. Length reasons will not post specific code, you can directly go to GitHub to see: remember- Scroll

packaging

The following should be the focus of this article, first to understand why packaging?

  1. Combine the JS files used in the project and output only one JS file.
  2. Make projects simultaneously supportedAMD.CMDBrowser,<script>Label introduction, i.eumdSpecification.
  3. With Babel, es6 syntax to ES5 syntax, compatible with lower versions of browsers.

PS: Since webpack and Babel update quickly, many tutorials on the web may be out of date. The current version (2019-03) is Babel 7.3.0 and WebPack 4.29.6. This article will only share the latest configuration methods, so this article will also be out of date. Readers, please note the version number.

NPM init program

Let’s create a new directory called remember-scroll, and then put the remember-scroll. Js into remember-scroll/ SRC/directory.

For general projects, the resource files are stored in the SRC directory. In order to look professional, it is best to rename remember-scroll.js to index.js.

The project does not yet have a package.json file, so initialize package.json in the root directory:

npm init
Copy the code

Fill in some information about the project as prompted.

Install Webpack and WebPack-CLI

When running webpack command, you need to install webpack-cli:

npm i webpack webpack-cli -D
Copy the code

Configuration webpack. Config. Js

Add webpack.config.js to the root directory and follow the example code from webPack’s official website:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'remember-scroll.js'// Change the output name}};Copy the code

Then configure the command to run webpack in package.json’s script:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"."dev": "webpack --mode=development --colors"
  },
Copy the code

Dist /remember-scroll.js is automatically generated by running NPM run dev in the root directory.

At this point we have achieved our first small goal: to make it 100 million, oh no, to combine storage.js and index.js into a single remember-scroll.js output.

This simple packaging can be called: modular packaging. Because we did not export the module itself through AMD’s return or CommonJS exports or this in the JS file, we could only execute the code when the module was imported and could not assign the module to other modules.

Supports UMD specifications

I believe many students have heard AMD,CommonJS specification, do not know the students can see Ruan Yifeng teacher’s introduction: Javascript modular programming (two) : AMD specification.

In order for our plugin to support both AMD and CommonJS, we need to package our plugin as a UMD common module.

I’ve read a previous article about how to define a high-powered native JS plug-in that requires hand-written support for modularity in the plug-in when not packaged with WebPack:

/ / reference from: https://www.jianshu.com/p/e65c246beac1; (function(undefined) {
    "use strict"var _global; var plugin = { // ... } // Finally expose the plug-in object to the global object _global = (function() {return this || (0, eval) ('this'); } ());if(typeof module ! = ="undefined" && module.exports) {
        module.exports = plugin;
    } else if (typeof define === "function" && define.amd) {
        define(function() {returnplugin; }); }else {
        !('plugin' in _global) && (_global.plugin = plugin);
    }
}());
Copy the code

The blogger sees this tuo thing, also be a little dizzy, have to admire big guy is big guy. Fortunately, webpack is now available. We just need to write the body key code, and WebPack will take care of the packaging for us.

In Webpack 4, we can pack JS as a Library, see Webpack Expose the Library. In our case, we simply add the library property to output:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'remember-scroll.js',
    library: 'RememberScroll',
    libraryTarget: 'umd',
    libraryExport: 'default'}};Copy the code

Note that the libraryTarget is UMD, which is the target specification we want to package.

When we import the js in HTML with the script tag, the RememberScroll variable is registered under the window (similar to registering the $variable globally when we import jQuery). At this point, just use the RememberScroll variable.

<script src=".. /dist/remember-scroll.js"></script>
<script>
  console.log(RememberScroll)
</script>
Copy the code

If libraryExport: ‘default’ is not added, export default scroll will look like this:

{
    'default': {
        initScroll() {}}}Copy the code

And here’s what we expect:

{
    initScroll() {}}Copy the code

That is, we want to print the content in default directly, not one layer behind default. Add libraryExport: ‘default’, which outputs only the contents of default when packaged.

PS: Webpack English document is a little bit confused, this pit let the blogger flounders for a long time to get up, so special mention. For those who are just interested, take a look at the document output.libraryexport.

At this point, we have achieved our second small goal: supporting the UMD specification.

Use the Babel – loader

The js package above already works in browsers that support ES6 syntax, such as Chrome. But to run in IE10, IE11, we need Babel to help us.

PS: although many people say that compatibility with IE is not considered, but as a general library, the antique IE7,8,9 can not be compatible, but the newer versions of IE10,11 still need to be compatible.

Babel is a JavaScript translator that you’ve probably heard of. As JavaScript continues to evolve, but browsers don’t keep up with it, new syntax and features aren’t immediately supported by browsers, so a translator is needed to translate new syntax and features into a syntax that modern browsers can understand, and Babel is a translator for that.

PS: Bloggers used to think (and I believe many of you who are new to Babel) that if you use Babel, you can use ES6 syntax painlessly, but this is not the case. Babel compilation does not polyfill. To ensure correct semantics, Babel can only transform the syntax without adding or modifying existing properties and methods. In order to use ES6 painlessly, polyfill is also needed. For those of you who don’t understand, I recommend reading this article: 21 minutes to Master the Front-end Polyfill scheme. It’s written very easily.

In general, Babel needs to be used with Polyfill.

Babel is updated frequently, and many of the configuration tutorials found on the web are old versions of Babel 7.x, which may not be suitable for the latest Babel 7.x, so let’s try out the latest Babel configuration solution: babel-loader. 1. Install babel-loader,@babel/core and @babel/preset-env.

npm install -D babel-loader @babel/core @babel/preset-env core-js
Copy the code

Core-js is a JavaScript modular standard library, and functions in core-js are used for on-demand packaging in @babel/preset-env, so install this as well, otherwise an error will be reported during packaging.

2. Modify the webpack.config.js configuration and add rules

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'remember-scroll.js',
    library: 'RememberScroll',
    libraryTarget: 'umd',
    libraryExport: 'default'
  },
  module: {
    rules: [
        {
          test: /\.m? js$/, exclude: /(node_modules|bower_components)/, use: { loader:'babel-loader'}}]}};Copy the code

The code representing.js is packaged using babel-loader.

3. Create babel.config.js in the root directory and refer to the Babel official website

const presets = [
  [
    "@babel/env",
    {
      targets: {
        browsers: [
            "last 1 version"."1%" >."maintained node versions"."not dead"
          ]
      },
      useBuiltIns: "usage",}]];Copy the code

Browsers are configured with target browsers, which browsers we want to be compatible with. F we want to be compatible with IE10, for example. We write IE10 and Webpack automatically adds Polyfill to our library for IE10 compatibility at package time.

The blogger uses the recommended argument from NPM Browserslist, which works with most browsers.

Once configured, NPM run dev can be packaged. At this point, we’ve achieved our third small goal: compatibility with older browsers.

Production environment packaging

The JS packaged by NPM Run Dev is relatively large and generally needs to be compressed. However, we can use the Production mode of Webpack to automatically compress JS for us and output a package available in the production environment. Add another build command to package.json:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"."build": "webpack --mode=production -o dist/remember-scroll.min.js --colors"."dev": "webpack --mode=development --colors"
  },
Copy the code

The output file name is also specified: remember-scroll.min.js, which is usually used in production environments.

Published to the NPM

After the above steps, we have written this library, students who have requirements can publish the library to NPM, so that more people can conveniently use your library.

Before publishing to NPM, you need to modify package.json, improve the description of the author and other information, and most importantly add the main entry file:

{
    "main": "dist/remember-scroll.min.js",}Copy the code

Remember -scroll.min.js by simply importing RememberScroll from ‘remember-scroll’ when someone else is using your library.

Release steps:

  1. First go to www.npmjs.com/ to register an account, then verify the email.
  2. Then on the command line type:npm adduser, enter your account password and email address to log in.
  3. runnpm publishUpload your package and you’ll find your package on NPM in a few minutes.

At this point, the basic completion of a plug-in development and release process.

A good open source project, however, should also have detailed documentation, use examples, etc. You can refer to the blogger this project readme.md, Chinese readme.md.

The last

The article has been written for several days, which is a great effort. Although it is worded, it should be quite clear that how to use ES6 syntax to write a JS plug-in from scratch to remember where the user left. It also explains in detail how to use the latest Webpack to package our library. Hope to let everyone harvest, also hope you can go to GitHub to click a Star to encourage it.

Remember – Scroll plugin was actually released to NPM a few months ago, I was too busy (lazy) to write a chapter to share. Simple but sincere, compatible with IE9.

It is also very easy to use. You can import a RememberScroll from ‘remember-scroll’ in vue directly with the script tag CDN. Detailed usage examples are provided in the documentation:

  • Script Label usage
  • Use mode in VUE
  • Vue Data acquisition mode asynchronously

Project address Github, online Demo.

Welcome everyone comment exchange, also welcome PR, at the same time hope everyone can click a Star to encourage.